diff options
Diffstat (limited to 'drivers/rtc/rtc-nuc900.c')
-rw-r--r-- | drivers/rtc/rtc-nuc900.c | 343 |
1 files changed, 343 insertions, 0 deletions
diff --git a/drivers/rtc/rtc-nuc900.c b/drivers/rtc/rtc-nuc900.c new file mode 100644 index 000000000000..a351bd5d8176 --- /dev/null +++ b/drivers/rtc/rtc-nuc900.c | |||
@@ -0,0 +1,343 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2008-2009 Nuvoton technology corporation. | ||
3 | * | ||
4 | * Wan ZongShun <mcuos.com@gmail.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation;version 2 of the License. | ||
9 | * | ||
10 | */ | ||
11 | |||
12 | #include <linux/module.h> | ||
13 | #include <linux/init.h> | ||
14 | #include <linux/platform_device.h> | ||
15 | #include <linux/slab.h> | ||
16 | #include <linux/rtc.h> | ||
17 | #include <linux/delay.h> | ||
18 | #include <linux/io.h> | ||
19 | #include <linux/bcd.h> | ||
20 | |||
21 | /* RTC Control Registers */ | ||
22 | #define REG_RTC_INIR 0x00 | ||
23 | #define REG_RTC_AER 0x04 | ||
24 | #define REG_RTC_FCR 0x08 | ||
25 | #define REG_RTC_TLR 0x0C | ||
26 | #define REG_RTC_CLR 0x10 | ||
27 | #define REG_RTC_TSSR 0x14 | ||
28 | #define REG_RTC_DWR 0x18 | ||
29 | #define REG_RTC_TAR 0x1C | ||
30 | #define REG_RTC_CAR 0x20 | ||
31 | #define REG_RTC_LIR 0x24 | ||
32 | #define REG_RTC_RIER 0x28 | ||
33 | #define REG_RTC_RIIR 0x2C | ||
34 | #define REG_RTC_TTR 0x30 | ||
35 | |||
36 | #define RTCSET 0x01 | ||
37 | #define AERRWENB 0x10000 | ||
38 | #define INIRRESET 0xa5eb1357 | ||
39 | #define AERPOWERON 0xA965 | ||
40 | #define AERPOWEROFF 0x0000 | ||
41 | #define LEAPYEAR 0x0001 | ||
42 | #define TICKENB 0x80 | ||
43 | #define TICKINTENB 0x0002 | ||
44 | #define ALARMINTENB 0x0001 | ||
45 | #define MODE24 0x0001 | ||
46 | |||
47 | struct nuc900_rtc { | ||
48 | int irq_num; | ||
49 | void __iomem *rtc_reg; | ||
50 | struct rtc_device *rtcdev; | ||
51 | }; | ||
52 | |||
53 | struct nuc900_bcd_time { | ||
54 | int bcd_sec; | ||
55 | int bcd_min; | ||
56 | int bcd_hour; | ||
57 | int bcd_mday; | ||
58 | int bcd_mon; | ||
59 | int bcd_year; | ||
60 | }; | ||
61 | |||
62 | static irqreturn_t nuc900_rtc_interrupt(int irq, void *_rtc) | ||
63 | { | ||
64 | struct nuc900_rtc *rtc = _rtc; | ||
65 | unsigned long events = 0, rtc_irq; | ||
66 | |||
67 | rtc_irq = __raw_readl(rtc->rtc_reg + REG_RTC_RIIR); | ||
68 | |||
69 | if (rtc_irq & ALARMINTENB) { | ||
70 | rtc_irq &= ~ALARMINTENB; | ||
71 | __raw_writel(rtc_irq, rtc->rtc_reg + REG_RTC_RIIR); | ||
72 | events |= RTC_AF | RTC_IRQF; | ||
73 | } | ||
74 | |||
75 | if (rtc_irq & TICKINTENB) { | ||
76 | rtc_irq &= ~TICKINTENB; | ||
77 | __raw_writel(rtc_irq, rtc->rtc_reg + REG_RTC_RIIR); | ||
78 | events |= RTC_UF | RTC_IRQF; | ||
79 | } | ||
80 | |||
81 | rtc_update_irq(rtc->rtcdev, 1, events); | ||
82 | |||
83 | return IRQ_HANDLED; | ||
84 | } | ||
85 | |||
86 | static int *check_rtc_access_enable(struct nuc900_rtc *nuc900_rtc) | ||
87 | { | ||
88 | unsigned int i; | ||
89 | __raw_writel(INIRRESET, nuc900_rtc->rtc_reg + REG_RTC_INIR); | ||
90 | |||
91 | mdelay(10); | ||
92 | |||
93 | __raw_writel(AERPOWERON, nuc900_rtc->rtc_reg + REG_RTC_AER); | ||
94 | |||
95 | for (i = 0; i < 1000; i++) { | ||
96 | if (__raw_readl(nuc900_rtc->rtc_reg + REG_RTC_AER) & AERRWENB) | ||
97 | return 0; | ||
98 | } | ||
99 | |||
100 | if ((__raw_readl(nuc900_rtc->rtc_reg + REG_RTC_AER) & AERRWENB) == 0x0) | ||
101 | return ERR_PTR(-ENODEV); | ||
102 | |||
103 | return ERR_PTR(-EPERM); | ||
104 | } | ||
105 | |||
106 | static void nuc900_rtc_bcd2bin(unsigned int timereg, | ||
107 | unsigned int calreg, struct rtc_time *tm) | ||
108 | { | ||
109 | tm->tm_mday = bcd2bin(calreg >> 0); | ||
110 | tm->tm_mon = bcd2bin(calreg >> 8); | ||
111 | tm->tm_year = bcd2bin(calreg >> 16) + 100; | ||
112 | |||
113 | tm->tm_sec = bcd2bin(timereg >> 0); | ||
114 | tm->tm_min = bcd2bin(timereg >> 8); | ||
115 | tm->tm_hour = bcd2bin(timereg >> 16); | ||
116 | |||
117 | rtc_valid_tm(tm); | ||
118 | } | ||
119 | |||
120 | static void nuc900_rtc_bin2bcd(struct rtc_time *settm, | ||
121 | struct nuc900_bcd_time *gettm) | ||
122 | { | ||
123 | gettm->bcd_mday = bin2bcd(settm->tm_mday) << 0; | ||
124 | gettm->bcd_mon = bin2bcd(settm->tm_mon) << 8; | ||
125 | gettm->bcd_year = bin2bcd(settm->tm_year - 100) << 16; | ||
126 | |||
127 | gettm->bcd_sec = bin2bcd(settm->tm_sec) << 0; | ||
128 | gettm->bcd_min = bin2bcd(settm->tm_min) << 8; | ||
129 | gettm->bcd_hour = bin2bcd(settm->tm_hour) << 16; | ||
130 | } | ||
131 | |||
132 | static int nuc900_update_irq_enable(struct device *dev, unsigned int enabled) | ||
133 | { | ||
134 | struct nuc900_rtc *rtc = dev_get_drvdata(dev); | ||
135 | |||
136 | if (enabled) | ||
137 | __raw_writel(__raw_readl(rtc->rtc_reg + REG_RTC_RIER)| | ||
138 | (TICKINTENB), rtc->rtc_reg + REG_RTC_RIER); | ||
139 | else | ||
140 | __raw_writel(__raw_readl(rtc->rtc_reg + REG_RTC_RIER)& | ||
141 | (~TICKINTENB), rtc->rtc_reg + REG_RTC_RIER); | ||
142 | |||
143 | return 0; | ||
144 | } | ||
145 | |||
146 | static int nuc900_alarm_irq_enable(struct device *dev, unsigned int enabled) | ||
147 | { | ||
148 | struct nuc900_rtc *rtc = dev_get_drvdata(dev); | ||
149 | |||
150 | if (enabled) | ||
151 | __raw_writel(__raw_readl(rtc->rtc_reg + REG_RTC_RIER)| | ||
152 | (ALARMINTENB), rtc->rtc_reg + REG_RTC_RIER); | ||
153 | else | ||
154 | __raw_writel(__raw_readl(rtc->rtc_reg + REG_RTC_RIER)& | ||
155 | (~ALARMINTENB), rtc->rtc_reg + REG_RTC_RIER); | ||
156 | |||
157 | return 0; | ||
158 | } | ||
159 | |||
160 | static int nuc900_rtc_read_time(struct device *dev, struct rtc_time *tm) | ||
161 | { | ||
162 | struct nuc900_rtc *rtc = dev_get_drvdata(dev); | ||
163 | unsigned int timeval, clrval; | ||
164 | |||
165 | timeval = __raw_readl(rtc->rtc_reg + REG_RTC_TLR); | ||
166 | clrval = __raw_readl(rtc->rtc_reg + REG_RTC_CLR); | ||
167 | |||
168 | nuc900_rtc_bcd2bin(timeval, clrval, tm); | ||
169 | |||
170 | return 0; | ||
171 | } | ||
172 | |||
173 | static int nuc900_rtc_set_time(struct device *dev, struct rtc_time *tm) | ||
174 | { | ||
175 | struct nuc900_rtc *rtc = dev_get_drvdata(dev); | ||
176 | struct nuc900_bcd_time gettm; | ||
177 | unsigned long val; | ||
178 | int *err; | ||
179 | |||
180 | nuc900_rtc_bin2bcd(tm, &gettm); | ||
181 | |||
182 | err = check_rtc_access_enable(rtc); | ||
183 | if (IS_ERR(err)) | ||
184 | return PTR_ERR(err); | ||
185 | |||
186 | val = gettm.bcd_mday | gettm.bcd_mon | gettm.bcd_year; | ||
187 | __raw_writel(val, rtc->rtc_reg + REG_RTC_CLR); | ||
188 | |||
189 | val = gettm.bcd_sec | gettm.bcd_min | gettm.bcd_hour; | ||
190 | __raw_writel(val, rtc->rtc_reg + REG_RTC_TLR); | ||
191 | |||
192 | return 0; | ||
193 | } | ||
194 | |||
195 | static int nuc900_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) | ||
196 | { | ||
197 | struct nuc900_rtc *rtc = dev_get_drvdata(dev); | ||
198 | unsigned int timeval, carval; | ||
199 | |||
200 | timeval = __raw_readl(rtc->rtc_reg + REG_RTC_TAR); | ||
201 | carval = __raw_readl(rtc->rtc_reg + REG_RTC_CAR); | ||
202 | |||
203 | nuc900_rtc_bcd2bin(timeval, carval, &alrm->time); | ||
204 | |||
205 | return 0; | ||
206 | } | ||
207 | |||
208 | static int nuc900_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) | ||
209 | { | ||
210 | struct nuc900_rtc *rtc = dev_get_drvdata(dev); | ||
211 | struct nuc900_bcd_time tm; | ||
212 | unsigned long val; | ||
213 | int *err; | ||
214 | |||
215 | nuc900_rtc_bin2bcd(&alrm->time, &tm); | ||
216 | |||
217 | err = check_rtc_access_enable(rtc); | ||
218 | if (IS_ERR(err)) | ||
219 | return PTR_ERR(err); | ||
220 | |||
221 | val = tm.bcd_mday | tm.bcd_mon | tm.bcd_year; | ||
222 | __raw_writel(val, rtc->rtc_reg + REG_RTC_CAR); | ||
223 | |||
224 | val = tm.bcd_sec | tm.bcd_min | tm.bcd_hour; | ||
225 | __raw_writel(val, rtc->rtc_reg + REG_RTC_TAR); | ||
226 | |||
227 | return 0; | ||
228 | } | ||
229 | |||
230 | static struct rtc_class_ops nuc900_rtc_ops = { | ||
231 | .read_time = nuc900_rtc_read_time, | ||
232 | .set_time = nuc900_rtc_set_time, | ||
233 | .read_alarm = nuc900_rtc_read_alarm, | ||
234 | .set_alarm = nuc900_rtc_set_alarm, | ||
235 | .alarm_irq_enable = nuc900_alarm_irq_enable, | ||
236 | .update_irq_enable = nuc900_update_irq_enable, | ||
237 | }; | ||
238 | |||
239 | static int __devinit nuc900_rtc_probe(struct platform_device *pdev) | ||
240 | { | ||
241 | struct resource *res; | ||
242 | struct nuc900_rtc *nuc900_rtc; | ||
243 | int err = 0; | ||
244 | |||
245 | nuc900_rtc = kzalloc(sizeof(struct nuc900_rtc), GFP_KERNEL); | ||
246 | if (!nuc900_rtc) { | ||
247 | dev_err(&pdev->dev, "kzalloc nuc900_rtc failed\n"); | ||
248 | return -ENOMEM; | ||
249 | } | ||
250 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
251 | if (!res) { | ||
252 | dev_err(&pdev->dev, "platform_get_resource failed\n"); | ||
253 | err = -ENXIO; | ||
254 | goto fail1; | ||
255 | } | ||
256 | |||
257 | if (!request_mem_region(res->start, resource_size(res), | ||
258 | pdev->name)) { | ||
259 | dev_err(&pdev->dev, "request_mem_region failed\n"); | ||
260 | err = -EBUSY; | ||
261 | goto fail1; | ||
262 | } | ||
263 | |||
264 | nuc900_rtc->rtc_reg = ioremap(res->start, resource_size(res)); | ||
265 | if (!nuc900_rtc->rtc_reg) { | ||
266 | dev_err(&pdev->dev, "ioremap rtc_reg failed\n"); | ||
267 | err = -ENOMEM; | ||
268 | goto fail2; | ||
269 | } | ||
270 | |||
271 | nuc900_rtc->irq_num = platform_get_irq(pdev, 0); | ||
272 | if (request_irq(nuc900_rtc->irq_num, nuc900_rtc_interrupt, | ||
273 | IRQF_DISABLED, "nuc900rtc", nuc900_rtc)) { | ||
274 | dev_err(&pdev->dev, "NUC900 RTC request irq failed\n"); | ||
275 | err = -EBUSY; | ||
276 | goto fail3; | ||
277 | } | ||
278 | |||
279 | nuc900_rtc->rtcdev = rtc_device_register(pdev->name, &pdev->dev, | ||
280 | &nuc900_rtc_ops, THIS_MODULE); | ||
281 | if (IS_ERR(nuc900_rtc->rtcdev)) { | ||
282 | dev_err(&pdev->dev, "rtc device register faild\n"); | ||
283 | err = PTR_ERR(nuc900_rtc->rtcdev); | ||
284 | goto fail4; | ||
285 | } | ||
286 | |||
287 | platform_set_drvdata(pdev, nuc900_rtc); | ||
288 | __raw_writel(__raw_readl(nuc900_rtc->rtc_reg + REG_RTC_TSSR) | MODE24, | ||
289 | nuc900_rtc->rtc_reg + REG_RTC_TSSR); | ||
290 | |||
291 | return 0; | ||
292 | |||
293 | fail4: free_irq(nuc900_rtc->irq_num, nuc900_rtc); | ||
294 | fail3: iounmap(nuc900_rtc->rtc_reg); | ||
295 | fail2: release_mem_region(res->start, resource_size(res)); | ||
296 | fail1: kfree(nuc900_rtc); | ||
297 | return err; | ||
298 | } | ||
299 | |||
300 | static int __devexit nuc900_rtc_remove(struct platform_device *pdev) | ||
301 | { | ||
302 | struct nuc900_rtc *nuc900_rtc = platform_get_drvdata(pdev); | ||
303 | struct resource *res; | ||
304 | |||
305 | rtc_device_unregister(nuc900_rtc->rtcdev); | ||
306 | free_irq(nuc900_rtc->irq_num, nuc900_rtc); | ||
307 | iounmap(nuc900_rtc->rtc_reg); | ||
308 | |||
309 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
310 | release_mem_region(res->start, resource_size(res)); | ||
311 | |||
312 | kfree(nuc900_rtc); | ||
313 | |||
314 | platform_set_drvdata(pdev, NULL); | ||
315 | |||
316 | return 0; | ||
317 | } | ||
318 | |||
319 | static struct platform_driver nuc900_rtc_driver = { | ||
320 | .remove = __devexit_p(nuc900_rtc_remove), | ||
321 | .driver = { | ||
322 | .name = "nuc900-rtc", | ||
323 | .owner = THIS_MODULE, | ||
324 | }, | ||
325 | }; | ||
326 | |||
327 | static int __init nuc900_rtc_init(void) | ||
328 | { | ||
329 | return platform_driver_probe(&nuc900_rtc_driver, nuc900_rtc_probe); | ||
330 | } | ||
331 | |||
332 | static void __exit nuc900_rtc_exit(void) | ||
333 | { | ||
334 | platform_driver_unregister(&nuc900_rtc_driver); | ||
335 | } | ||
336 | |||
337 | module_init(nuc900_rtc_init); | ||
338 | module_exit(nuc900_rtc_exit); | ||
339 | |||
340 | MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>"); | ||
341 | MODULE_DESCRIPTION("nuc910/nuc920 RTC driver"); | ||
342 | MODULE_LICENSE("GPL"); | ||
343 | MODULE_ALIAS("platform:nuc900-rtc"); | ||