diff options
Diffstat (limited to 'drivers/rtc/rtc-mpc5121.c')
-rw-r--r-- | drivers/rtc/rtc-mpc5121.c | 390 |
1 files changed, 390 insertions, 0 deletions
diff --git a/drivers/rtc/rtc-mpc5121.c b/drivers/rtc/rtc-mpc5121.c new file mode 100644 index 000000000000..dfcdf0901d21 --- /dev/null +++ b/drivers/rtc/rtc-mpc5121.c | |||
@@ -0,0 +1,390 @@ | |||
1 | /* | ||
2 | * Real-time clock driver for MPC5121 | ||
3 | * | ||
4 | * Copyright 2007, Domen Puncer <domen.puncer@telargo.com> | ||
5 | * Copyright 2008, Freescale Semiconductor, Inc. All rights reserved. | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License version 2 as | ||
9 | * published by the Free Software Foundation. | ||
10 | */ | ||
11 | |||
12 | #include <linux/init.h> | ||
13 | #include <linux/module.h> | ||
14 | #include <linux/rtc.h> | ||
15 | #include <linux/of_device.h> | ||
16 | #include <linux/of_platform.h> | ||
17 | #include <linux/io.h> | ||
18 | #include <linux/slab.h> | ||
19 | |||
20 | struct mpc5121_rtc_regs { | ||
21 | u8 set_time; /* RTC + 0x00 */ | ||
22 | u8 hour_set; /* RTC + 0x01 */ | ||
23 | u8 minute_set; /* RTC + 0x02 */ | ||
24 | u8 second_set; /* RTC + 0x03 */ | ||
25 | |||
26 | u8 set_date; /* RTC + 0x04 */ | ||
27 | u8 month_set; /* RTC + 0x05 */ | ||
28 | u8 weekday_set; /* RTC + 0x06 */ | ||
29 | u8 date_set; /* RTC + 0x07 */ | ||
30 | |||
31 | u8 write_sw; /* RTC + 0x08 */ | ||
32 | u8 sw_set; /* RTC + 0x09 */ | ||
33 | u16 year_set; /* RTC + 0x0a */ | ||
34 | |||
35 | u8 alm_enable; /* RTC + 0x0c */ | ||
36 | u8 alm_hour_set; /* RTC + 0x0d */ | ||
37 | u8 alm_min_set; /* RTC + 0x0e */ | ||
38 | u8 int_enable; /* RTC + 0x0f */ | ||
39 | |||
40 | u8 reserved1; | ||
41 | u8 hour; /* RTC + 0x11 */ | ||
42 | u8 minute; /* RTC + 0x12 */ | ||
43 | u8 second; /* RTC + 0x13 */ | ||
44 | |||
45 | u8 month; /* RTC + 0x14 */ | ||
46 | u8 wday_mday; /* RTC + 0x15 */ | ||
47 | u16 year; /* RTC + 0x16 */ | ||
48 | |||
49 | u8 int_alm; /* RTC + 0x18 */ | ||
50 | u8 int_sw; /* RTC + 0x19 */ | ||
51 | u8 alm_status; /* RTC + 0x1a */ | ||
52 | u8 sw_minute; /* RTC + 0x1b */ | ||
53 | |||
54 | u8 bus_error_1; /* RTC + 0x1c */ | ||
55 | u8 int_day; /* RTC + 0x1d */ | ||
56 | u8 int_min; /* RTC + 0x1e */ | ||
57 | u8 int_sec; /* RTC + 0x1f */ | ||
58 | |||
59 | /* | ||
60 | * target_time: | ||
61 | * intended to be used for hibernation but hibernation | ||
62 | * does not work on silicon rev 1.5 so use it for non-volatile | ||
63 | * storage of offset between the actual_time register and linux | ||
64 | * time | ||
65 | */ | ||
66 | u32 target_time; /* RTC + 0x20 */ | ||
67 | /* | ||
68 | * actual_time: | ||
69 | * readonly time since VBAT_RTC was last connected | ||
70 | */ | ||
71 | u32 actual_time; /* RTC + 0x24 */ | ||
72 | u32 keep_alive; /* RTC + 0x28 */ | ||
73 | }; | ||
74 | |||
75 | struct mpc5121_rtc_data { | ||
76 | unsigned irq; | ||
77 | unsigned irq_periodic; | ||
78 | struct mpc5121_rtc_regs __iomem *regs; | ||
79 | struct rtc_device *rtc; | ||
80 | struct rtc_wkalrm wkalarm; | ||
81 | }; | ||
82 | |||
83 | /* | ||
84 | * Update second/minute/hour registers. | ||
85 | * | ||
86 | * This is just so alarm will work. | ||
87 | */ | ||
88 | static void mpc5121_rtc_update_smh(struct mpc5121_rtc_regs __iomem *regs, | ||
89 | struct rtc_time *tm) | ||
90 | { | ||
91 | out_8(®s->second_set, tm->tm_sec); | ||
92 | out_8(®s->minute_set, tm->tm_min); | ||
93 | out_8(®s->hour_set, tm->tm_hour); | ||
94 | |||
95 | /* set time sequence */ | ||
96 | out_8(®s->set_time, 0x1); | ||
97 | out_8(®s->set_time, 0x3); | ||
98 | out_8(®s->set_time, 0x1); | ||
99 | out_8(®s->set_time, 0x0); | ||
100 | } | ||
101 | |||
102 | static int mpc5121_rtc_read_time(struct device *dev, struct rtc_time *tm) | ||
103 | { | ||
104 | struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev); | ||
105 | struct mpc5121_rtc_regs __iomem *regs = rtc->regs; | ||
106 | unsigned long now; | ||
107 | |||
108 | /* | ||
109 | * linux time is actual_time plus the offset saved in target_time | ||
110 | */ | ||
111 | now = in_be32(®s->actual_time) + in_be32(®s->target_time); | ||
112 | |||
113 | rtc_time_to_tm(now, tm); | ||
114 | |||
115 | /* | ||
116 | * update second minute hour registers | ||
117 | * so alarms will work | ||
118 | */ | ||
119 | mpc5121_rtc_update_smh(regs, tm); | ||
120 | |||
121 | return rtc_valid_tm(tm); | ||
122 | } | ||
123 | |||
124 | static int mpc5121_rtc_set_time(struct device *dev, struct rtc_time *tm) | ||
125 | { | ||
126 | struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev); | ||
127 | struct mpc5121_rtc_regs __iomem *regs = rtc->regs; | ||
128 | int ret; | ||
129 | unsigned long now; | ||
130 | |||
131 | /* | ||
132 | * The actual_time register is read only so we write the offset | ||
133 | * between it and linux time to the target_time register. | ||
134 | */ | ||
135 | ret = rtc_tm_to_time(tm, &now); | ||
136 | if (ret == 0) | ||
137 | out_be32(®s->target_time, now - in_be32(®s->actual_time)); | ||
138 | |||
139 | /* | ||
140 | * update second minute hour registers | ||
141 | * so alarms will work | ||
142 | */ | ||
143 | mpc5121_rtc_update_smh(regs, tm); | ||
144 | |||
145 | return 0; | ||
146 | } | ||
147 | |||
148 | static int mpc5121_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) | ||
149 | { | ||
150 | struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev); | ||
151 | struct mpc5121_rtc_regs __iomem *regs = rtc->regs; | ||
152 | |||
153 | *alarm = rtc->wkalarm; | ||
154 | |||
155 | alarm->pending = in_8(®s->alm_status); | ||
156 | |||
157 | return 0; | ||
158 | } | ||
159 | |||
160 | static int mpc5121_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) | ||
161 | { | ||
162 | struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev); | ||
163 | struct mpc5121_rtc_regs __iomem *regs = rtc->regs; | ||
164 | |||
165 | /* | ||
166 | * the alarm has no seconds so deal with it | ||
167 | */ | ||
168 | if (alarm->time.tm_sec) { | ||
169 | alarm->time.tm_sec = 0; | ||
170 | alarm->time.tm_min++; | ||
171 | if (alarm->time.tm_min >= 60) { | ||
172 | alarm->time.tm_min = 0; | ||
173 | alarm->time.tm_hour++; | ||
174 | if (alarm->time.tm_hour >= 24) | ||
175 | alarm->time.tm_hour = 0; | ||
176 | } | ||
177 | } | ||
178 | |||
179 | alarm->time.tm_mday = -1; | ||
180 | alarm->time.tm_mon = -1; | ||
181 | alarm->time.tm_year = -1; | ||
182 | |||
183 | out_8(®s->alm_min_set, alarm->time.tm_min); | ||
184 | out_8(®s->alm_hour_set, alarm->time.tm_hour); | ||
185 | |||
186 | out_8(®s->alm_enable, alarm->enabled); | ||
187 | |||
188 | rtc->wkalarm = *alarm; | ||
189 | return 0; | ||
190 | } | ||
191 | |||
192 | static irqreturn_t mpc5121_rtc_handler(int irq, void *dev) | ||
193 | { | ||
194 | struct mpc5121_rtc_data *rtc = dev_get_drvdata((struct device *)dev); | ||
195 | struct mpc5121_rtc_regs __iomem *regs = rtc->regs; | ||
196 | |||
197 | if (in_8(®s->int_alm)) { | ||
198 | /* acknowledge and clear status */ | ||
199 | out_8(®s->int_alm, 1); | ||
200 | out_8(®s->alm_status, 1); | ||
201 | |||
202 | rtc_update_irq(rtc->rtc, 1, RTC_IRQF | RTC_AF); | ||
203 | return IRQ_HANDLED; | ||
204 | } | ||
205 | |||
206 | return IRQ_NONE; | ||
207 | } | ||
208 | |||
209 | static irqreturn_t mpc5121_rtc_handler_upd(int irq, void *dev) | ||
210 | { | ||
211 | struct mpc5121_rtc_data *rtc = dev_get_drvdata((struct device *)dev); | ||
212 | struct mpc5121_rtc_regs __iomem *regs = rtc->regs; | ||
213 | |||
214 | if (in_8(®s->int_sec) && (in_8(®s->int_enable) & 0x1)) { | ||
215 | /* acknowledge */ | ||
216 | out_8(®s->int_sec, 1); | ||
217 | |||
218 | rtc_update_irq(rtc->rtc, 1, RTC_IRQF | RTC_UF); | ||
219 | return IRQ_HANDLED; | ||
220 | } | ||
221 | |||
222 | return IRQ_NONE; | ||
223 | } | ||
224 | |||
225 | static int mpc5121_rtc_alarm_irq_enable(struct device *dev, | ||
226 | unsigned int enabled) | ||
227 | { | ||
228 | struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev); | ||
229 | struct mpc5121_rtc_regs __iomem *regs = rtc->regs; | ||
230 | int val; | ||
231 | |||
232 | if (enabled) | ||
233 | val = 1; | ||
234 | else | ||
235 | val = 0; | ||
236 | |||
237 | out_8(®s->alm_enable, val); | ||
238 | rtc->wkalarm.enabled = val; | ||
239 | |||
240 | return 0; | ||
241 | } | ||
242 | |||
243 | static int mpc5121_rtc_update_irq_enable(struct device *dev, | ||
244 | unsigned int enabled) | ||
245 | { | ||
246 | struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev); | ||
247 | struct mpc5121_rtc_regs __iomem *regs = rtc->regs; | ||
248 | int val; | ||
249 | |||
250 | val = in_8(®s->int_enable); | ||
251 | |||
252 | if (enabled) | ||
253 | val = (val & ~0x8) | 0x1; | ||
254 | else | ||
255 | val &= ~0x1; | ||
256 | |||
257 | out_8(®s->int_enable, val); | ||
258 | |||
259 | return 0; | ||
260 | } | ||
261 | |||
262 | static const struct rtc_class_ops mpc5121_rtc_ops = { | ||
263 | .read_time = mpc5121_rtc_read_time, | ||
264 | .set_time = mpc5121_rtc_set_time, | ||
265 | .read_alarm = mpc5121_rtc_read_alarm, | ||
266 | .set_alarm = mpc5121_rtc_set_alarm, | ||
267 | .alarm_irq_enable = mpc5121_rtc_alarm_irq_enable, | ||
268 | .update_irq_enable = mpc5121_rtc_update_irq_enable, | ||
269 | }; | ||
270 | |||
271 | static int __devinit mpc5121_rtc_probe(struct platform_device *op, | ||
272 | const struct of_device_id *match) | ||
273 | { | ||
274 | struct mpc5121_rtc_data *rtc; | ||
275 | int err = 0; | ||
276 | u32 ka; | ||
277 | |||
278 | rtc = kzalloc(sizeof(*rtc), GFP_KERNEL); | ||
279 | if (!rtc) | ||
280 | return -ENOMEM; | ||
281 | |||
282 | rtc->regs = of_iomap(op->dev.of_node, 0); | ||
283 | if (!rtc->regs) { | ||
284 | dev_err(&op->dev, "%s: couldn't map io space\n", __func__); | ||
285 | err = -ENOSYS; | ||
286 | goto out_free; | ||
287 | } | ||
288 | |||
289 | device_init_wakeup(&op->dev, 1); | ||
290 | |||
291 | dev_set_drvdata(&op->dev, rtc); | ||
292 | |||
293 | rtc->irq = irq_of_parse_and_map(op->dev.of_node, 1); | ||
294 | err = request_irq(rtc->irq, mpc5121_rtc_handler, IRQF_DISABLED, | ||
295 | "mpc5121-rtc", &op->dev); | ||
296 | if (err) { | ||
297 | dev_err(&op->dev, "%s: could not request irq: %i\n", | ||
298 | __func__, rtc->irq); | ||
299 | goto out_dispose; | ||
300 | } | ||
301 | |||
302 | rtc->irq_periodic = irq_of_parse_and_map(op->dev.of_node, 0); | ||
303 | err = request_irq(rtc->irq_periodic, mpc5121_rtc_handler_upd, | ||
304 | IRQF_DISABLED, "mpc5121-rtc_upd", &op->dev); | ||
305 | if (err) { | ||
306 | dev_err(&op->dev, "%s: could not request irq: %i\n", | ||
307 | __func__, rtc->irq_periodic); | ||
308 | goto out_dispose2; | ||
309 | } | ||
310 | |||
311 | ka = in_be32(&rtc->regs->keep_alive); | ||
312 | if (ka & 0x02) { | ||
313 | dev_warn(&op->dev, | ||
314 | "mpc5121-rtc: Battery or oscillator failure!\n"); | ||
315 | out_be32(&rtc->regs->keep_alive, ka); | ||
316 | } | ||
317 | |||
318 | rtc->rtc = rtc_device_register("mpc5121-rtc", &op->dev, | ||
319 | &mpc5121_rtc_ops, THIS_MODULE); | ||
320 | if (IS_ERR(rtc->rtc)) { | ||
321 | err = PTR_ERR(rtc->rtc); | ||
322 | goto out_free_irq; | ||
323 | } | ||
324 | |||
325 | return 0; | ||
326 | |||
327 | out_free_irq: | ||
328 | free_irq(rtc->irq_periodic, &op->dev); | ||
329 | out_dispose2: | ||
330 | irq_dispose_mapping(rtc->irq_periodic); | ||
331 | free_irq(rtc->irq, &op->dev); | ||
332 | out_dispose: | ||
333 | irq_dispose_mapping(rtc->irq); | ||
334 | iounmap(rtc->regs); | ||
335 | out_free: | ||
336 | kfree(rtc); | ||
337 | |||
338 | return err; | ||
339 | } | ||
340 | |||
341 | static int __devexit mpc5121_rtc_remove(struct platform_device *op) | ||
342 | { | ||
343 | struct mpc5121_rtc_data *rtc = dev_get_drvdata(&op->dev); | ||
344 | struct mpc5121_rtc_regs __iomem *regs = rtc->regs; | ||
345 | |||
346 | /* disable interrupt, so there are no nasty surprises */ | ||
347 | out_8(®s->alm_enable, 0); | ||
348 | out_8(®s->int_enable, in_8(®s->int_enable) & ~0x1); | ||
349 | |||
350 | rtc_device_unregister(rtc->rtc); | ||
351 | iounmap(rtc->regs); | ||
352 | free_irq(rtc->irq, &op->dev); | ||
353 | free_irq(rtc->irq_periodic, &op->dev); | ||
354 | irq_dispose_mapping(rtc->irq); | ||
355 | irq_dispose_mapping(rtc->irq_periodic); | ||
356 | dev_set_drvdata(&op->dev, NULL); | ||
357 | kfree(rtc); | ||
358 | |||
359 | return 0; | ||
360 | } | ||
361 | |||
362 | static struct of_device_id mpc5121_rtc_match[] __devinitdata = { | ||
363 | { .compatible = "fsl,mpc5121-rtc", }, | ||
364 | {}, | ||
365 | }; | ||
366 | |||
367 | static struct of_platform_driver mpc5121_rtc_driver = { | ||
368 | .driver = { | ||
369 | .name = "mpc5121-rtc", | ||
370 | .owner = THIS_MODULE, | ||
371 | .of_match_table = mpc5121_rtc_match, | ||
372 | }, | ||
373 | .probe = mpc5121_rtc_probe, | ||
374 | .remove = __devexit_p(mpc5121_rtc_remove), | ||
375 | }; | ||
376 | |||
377 | static int __init mpc5121_rtc_init(void) | ||
378 | { | ||
379 | return of_register_platform_driver(&mpc5121_rtc_driver); | ||
380 | } | ||
381 | module_init(mpc5121_rtc_init); | ||
382 | |||
383 | static void __exit mpc5121_rtc_exit(void) | ||
384 | { | ||
385 | of_unregister_platform_driver(&mpc5121_rtc_driver); | ||
386 | } | ||
387 | module_exit(mpc5121_rtc_exit); | ||
388 | |||
389 | MODULE_LICENSE("GPL"); | ||
390 | MODULE_AUTHOR("John Rigby <jcrigby@gmail.com>"); | ||