Root/
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"); |
344 |
Branches:
ben-wpan
ben-wpan-stefan
javiroman/ks7010
jz-2.6.34
jz-2.6.34-rc5
jz-2.6.34-rc6
jz-2.6.34-rc7
jz-2.6.35
jz-2.6.36
jz-2.6.37
jz-2.6.38
jz-2.6.39
jz-3.0
jz-3.1
jz-3.11
jz-3.12
jz-3.13
jz-3.15
jz-3.16
jz-3.18-dt
jz-3.2
jz-3.3
jz-3.4
jz-3.5
jz-3.6
jz-3.6-rc2-pwm
jz-3.9
jz-3.9-clk
jz-3.9-rc8
jz47xx
jz47xx-2.6.38
master
Tags:
od-2011-09-04
od-2011-09-18
v2.6.34-rc5
v2.6.34-rc6
v2.6.34-rc7
v3.9