diff options
Diffstat (limited to 'drivers/rtc/rtc-ds3232.c')
-rw-r--r-- | drivers/rtc/rtc-ds3232.c | 505 |
1 files changed, 505 insertions, 0 deletions
diff --git a/drivers/rtc/rtc-ds3232.c b/drivers/rtc/rtc-ds3232.c new file mode 100644 index 000000000000..23a9ee19764c --- /dev/null +++ b/drivers/rtc/rtc-ds3232.c | |||
@@ -0,0 +1,505 @@ | |||
1 | /* | ||
2 | * RTC client/driver for the Maxim/Dallas DS3232 Real-Time Clock over I2C | ||
3 | * | ||
4 | * Copyright (C) 2009-2010 Freescale Semiconductor. | ||
5 | * Author: Jack Lan <jack.lan@freescale.com> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify it | ||
8 | * under the terms of the GNU General Public License as published by the | ||
9 | * Free Software Foundation; either version 2 of the License, or (at your | ||
10 | * option) any later version. | ||
11 | */ | ||
12 | /* | ||
13 | * It would be more efficient to use i2c msgs/i2c_transfer directly but, as | ||
14 | * recommened in .../Documentation/i2c/writing-clients section | ||
15 | * "Sending and receiving", using SMBus level communication is preferred. | ||
16 | */ | ||
17 | |||
18 | #include <linux/kernel.h> | ||
19 | #include <linux/module.h> | ||
20 | #include <linux/interrupt.h> | ||
21 | #include <linux/i2c.h> | ||
22 | #include <linux/rtc.h> | ||
23 | #include <linux/bcd.h> | ||
24 | #include <linux/workqueue.h> | ||
25 | #include <linux/slab.h> | ||
26 | |||
27 | #define DS3232_REG_SECONDS 0x00 | ||
28 | #define DS3232_REG_MINUTES 0x01 | ||
29 | #define DS3232_REG_HOURS 0x02 | ||
30 | #define DS3232_REG_AMPM 0x02 | ||
31 | #define DS3232_REG_DAY 0x03 | ||
32 | #define DS3232_REG_DATE 0x04 | ||
33 | #define DS3232_REG_MONTH 0x05 | ||
34 | #define DS3232_REG_CENTURY 0x05 | ||
35 | #define DS3232_REG_YEAR 0x06 | ||
36 | #define DS3232_REG_ALARM1 0x07 /* Alarm 1 BASE */ | ||
37 | #define DS3232_REG_ALARM2 0x0B /* Alarm 2 BASE */ | ||
38 | #define DS3232_REG_CR 0x0E /* Control register */ | ||
39 | # define DS3232_REG_CR_nEOSC 0x80 | ||
40 | # define DS3232_REG_CR_INTCN 0x04 | ||
41 | # define DS3232_REG_CR_A2IE 0x02 | ||
42 | # define DS3232_REG_CR_A1IE 0x01 | ||
43 | |||
44 | #define DS3232_REG_SR 0x0F /* control/status register */ | ||
45 | # define DS3232_REG_SR_OSF 0x80 | ||
46 | # define DS3232_REG_SR_BSY 0x04 | ||
47 | # define DS3232_REG_SR_A2F 0x02 | ||
48 | # define DS3232_REG_SR_A1F 0x01 | ||
49 | |||
50 | struct ds3232 { | ||
51 | struct i2c_client *client; | ||
52 | struct rtc_device *rtc; | ||
53 | struct work_struct work; | ||
54 | |||
55 | /* The mutex protects alarm operations, and prevents a race | ||
56 | * between the enable_irq() in the workqueue and the free_irq() | ||
57 | * in the remove function. | ||
58 | */ | ||
59 | struct mutex mutex; | ||
60 | int exiting; | ||
61 | }; | ||
62 | |||
63 | static struct i2c_driver ds3232_driver; | ||
64 | |||
65 | static int ds3232_check_rtc_status(struct i2c_client *client) | ||
66 | { | ||
67 | int ret = 0; | ||
68 | int control, stat; | ||
69 | |||
70 | stat = i2c_smbus_read_byte_data(client, DS3232_REG_SR); | ||
71 | if (stat < 0) | ||
72 | return stat; | ||
73 | |||
74 | if (stat & DS3232_REG_SR_OSF) | ||
75 | dev_warn(&client->dev, | ||
76 | "oscillator discontinuity flagged, " | ||
77 | "time unreliable\n"); | ||
78 | |||
79 | stat &= ~(DS3232_REG_SR_OSF | DS3232_REG_SR_A1F | DS3232_REG_SR_A2F); | ||
80 | |||
81 | ret = i2c_smbus_write_byte_data(client, DS3232_REG_SR, stat); | ||
82 | if (ret < 0) | ||
83 | return ret; | ||
84 | |||
85 | /* If the alarm is pending, clear it before requesting | ||
86 | * the interrupt, so an interrupt event isn't reported | ||
87 | * before everything is initialized. | ||
88 | */ | ||
89 | |||
90 | control = i2c_smbus_read_byte_data(client, DS3232_REG_CR); | ||
91 | if (control < 0) | ||
92 | return control; | ||
93 | |||
94 | control &= ~(DS3232_REG_CR_A1IE | DS3232_REG_CR_A2IE); | ||
95 | control |= DS3232_REG_CR_INTCN; | ||
96 | |||
97 | return i2c_smbus_write_byte_data(client, DS3232_REG_CR, control); | ||
98 | } | ||
99 | |||
100 | static int ds3232_read_time(struct device *dev, struct rtc_time *time) | ||
101 | { | ||
102 | struct i2c_client *client = to_i2c_client(dev); | ||
103 | int ret; | ||
104 | u8 buf[7]; | ||
105 | unsigned int year, month, day, hour, minute, second; | ||
106 | unsigned int week, twelve_hr, am_pm; | ||
107 | unsigned int century, add_century = 0; | ||
108 | |||
109 | ret = i2c_smbus_read_i2c_block_data(client, DS3232_REG_SECONDS, 7, buf); | ||
110 | |||
111 | if (ret < 0) | ||
112 | return ret; | ||
113 | if (ret < 7) | ||
114 | return -EIO; | ||
115 | |||
116 | second = buf[0]; | ||
117 | minute = buf[1]; | ||
118 | hour = buf[2]; | ||
119 | week = buf[3]; | ||
120 | day = buf[4]; | ||
121 | month = buf[5]; | ||
122 | year = buf[6]; | ||
123 | |||
124 | /* Extract additional information for AM/PM and century */ | ||
125 | |||
126 | twelve_hr = hour & 0x40; | ||
127 | am_pm = hour & 0x20; | ||
128 | century = month & 0x80; | ||
129 | |||
130 | /* Write to rtc_time structure */ | ||
131 | |||
132 | time->tm_sec = bcd2bin(second); | ||
133 | time->tm_min = bcd2bin(minute); | ||
134 | if (twelve_hr) { | ||
135 | /* Convert to 24 hr */ | ||
136 | if (am_pm) | ||
137 | time->tm_hour = bcd2bin(hour & 0x1F) + 12; | ||
138 | else | ||
139 | time->tm_hour = bcd2bin(hour & 0x1F); | ||
140 | } else { | ||
141 | time->tm_hour = bcd2bin(hour); | ||
142 | } | ||
143 | |||
144 | time->tm_wday = bcd2bin(week); | ||
145 | time->tm_mday = bcd2bin(day); | ||
146 | time->tm_mon = bcd2bin(month & 0x7F); | ||
147 | if (century) | ||
148 | add_century = 100; | ||
149 | |||
150 | time->tm_year = bcd2bin(year) + add_century; | ||
151 | |||
152 | return rtc_valid_tm(time); | ||
153 | } | ||
154 | |||
155 | static int ds3232_set_time(struct device *dev, struct rtc_time *time) | ||
156 | { | ||
157 | struct i2c_client *client = to_i2c_client(dev); | ||
158 | u8 buf[7]; | ||
159 | |||
160 | /* Extract time from rtc_time and load into ds3232*/ | ||
161 | |||
162 | buf[0] = bin2bcd(time->tm_sec); | ||
163 | buf[1] = bin2bcd(time->tm_min); | ||
164 | buf[2] = bin2bcd(time->tm_hour); | ||
165 | buf[3] = bin2bcd(time->tm_wday); /* Day of the week */ | ||
166 | buf[4] = bin2bcd(time->tm_mday); /* Date */ | ||
167 | buf[5] = bin2bcd(time->tm_mon); | ||
168 | if (time->tm_year >= 100) { | ||
169 | buf[5] |= 0x80; | ||
170 | buf[6] = bin2bcd(time->tm_year - 100); | ||
171 | } else { | ||
172 | buf[6] = bin2bcd(time->tm_year); | ||
173 | } | ||
174 | |||
175 | return i2c_smbus_write_i2c_block_data(client, | ||
176 | DS3232_REG_SECONDS, 7, buf); | ||
177 | } | ||
178 | |||
179 | /* | ||
180 | * DS3232 has two alarm, we only use alarm1 | ||
181 | * According to linux specification, only support one-shot alarm | ||
182 | * no periodic alarm mode | ||
183 | */ | ||
184 | static int ds3232_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) | ||
185 | { | ||
186 | struct i2c_client *client = to_i2c_client(dev); | ||
187 | struct ds3232 *ds3232 = i2c_get_clientdata(client); | ||
188 | int control, stat; | ||
189 | int ret; | ||
190 | u8 buf[4]; | ||
191 | |||
192 | mutex_lock(&ds3232->mutex); | ||
193 | |||
194 | ret = i2c_smbus_read_byte_data(client, DS3232_REG_SR); | ||
195 | if (ret < 0) | ||
196 | goto out; | ||
197 | stat = ret; | ||
198 | ret = i2c_smbus_read_byte_data(client, DS3232_REG_CR); | ||
199 | if (ret < 0) | ||
200 | goto out; | ||
201 | control = ret; | ||
202 | ret = i2c_smbus_read_i2c_block_data(client, DS3232_REG_ALARM1, 4, buf); | ||
203 | if (ret < 0) | ||
204 | goto out; | ||
205 | |||
206 | alarm->time.tm_sec = bcd2bin(buf[0] & 0x7F); | ||
207 | alarm->time.tm_min = bcd2bin(buf[1] & 0x7F); | ||
208 | alarm->time.tm_hour = bcd2bin(buf[2] & 0x7F); | ||
209 | alarm->time.tm_mday = bcd2bin(buf[3] & 0x7F); | ||
210 | |||
211 | alarm->time.tm_mon = -1; | ||
212 | alarm->time.tm_year = -1; | ||
213 | alarm->time.tm_wday = -1; | ||
214 | alarm->time.tm_yday = -1; | ||
215 | alarm->time.tm_isdst = -1; | ||
216 | |||
217 | alarm->enabled = !!(control & DS3232_REG_CR_A1IE); | ||
218 | alarm->pending = !!(stat & DS3232_REG_SR_A1F); | ||
219 | |||
220 | ret = 0; | ||
221 | out: | ||
222 | mutex_unlock(&ds3232->mutex); | ||
223 | return ret; | ||
224 | } | ||
225 | |||
226 | /* | ||
227 | * linux rtc-module does not support wday alarm | ||
228 | * and only 24h time mode supported indeed | ||
229 | */ | ||
230 | static int ds3232_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) | ||
231 | { | ||
232 | struct i2c_client *client = to_i2c_client(dev); | ||
233 | struct ds3232 *ds3232 = i2c_get_clientdata(client); | ||
234 | int control, stat; | ||
235 | int ret; | ||
236 | u8 buf[4]; | ||
237 | |||
238 | if (client->irq <= 0) | ||
239 | return -EINVAL; | ||
240 | |||
241 | mutex_lock(&ds3232->mutex); | ||
242 | |||
243 | buf[0] = bin2bcd(alarm->time.tm_sec); | ||
244 | buf[1] = bin2bcd(alarm->time.tm_min); | ||
245 | buf[2] = bin2bcd(alarm->time.tm_hour); | ||
246 | buf[3] = bin2bcd(alarm->time.tm_mday); | ||
247 | |||
248 | /* clear alarm interrupt enable bit */ | ||
249 | ret = i2c_smbus_read_byte_data(client, DS3232_REG_CR); | ||
250 | if (ret < 0) | ||
251 | goto out; | ||
252 | control = ret; | ||
253 | control &= ~(DS3232_REG_CR_A1IE | DS3232_REG_CR_A2IE); | ||
254 | ret = i2c_smbus_write_byte_data(client, DS3232_REG_CR, control); | ||
255 | if (ret < 0) | ||
256 | goto out; | ||
257 | |||
258 | /* clear any pending alarm flag */ | ||
259 | ret = i2c_smbus_read_byte_data(client, DS3232_REG_SR); | ||
260 | if (ret < 0) | ||
261 | goto out; | ||
262 | stat = ret; | ||
263 | stat &= ~(DS3232_REG_SR_A1F | DS3232_REG_SR_A2F); | ||
264 | ret = i2c_smbus_write_byte_data(client, DS3232_REG_SR, stat); | ||
265 | if (ret < 0) | ||
266 | goto out; | ||
267 | |||
268 | ret = i2c_smbus_write_i2c_block_data(client, DS3232_REG_ALARM1, 4, buf); | ||
269 | |||
270 | if (alarm->enabled) { | ||
271 | control |= DS3232_REG_CR_A1IE; | ||
272 | ret = i2c_smbus_write_byte_data(client, DS3232_REG_CR, control); | ||
273 | } | ||
274 | out: | ||
275 | mutex_unlock(&ds3232->mutex); | ||
276 | return ret; | ||
277 | } | ||
278 | |||
279 | static void ds3232_update_alarm(struct i2c_client *client) | ||
280 | { | ||
281 | struct ds3232 *ds3232 = i2c_get_clientdata(client); | ||
282 | int control; | ||
283 | int ret; | ||
284 | u8 buf[4]; | ||
285 | |||
286 | mutex_lock(&ds3232->mutex); | ||
287 | |||
288 | ret = i2c_smbus_read_i2c_block_data(client, DS3232_REG_ALARM1, 4, buf); | ||
289 | if (ret < 0) | ||
290 | goto unlock; | ||
291 | |||
292 | buf[0] = bcd2bin(buf[0]) < 0 || (ds3232->rtc->irq_data & RTC_UF) ? | ||
293 | 0x80 : buf[0]; | ||
294 | buf[1] = bcd2bin(buf[1]) < 0 || (ds3232->rtc->irq_data & RTC_UF) ? | ||
295 | 0x80 : buf[1]; | ||
296 | buf[2] = bcd2bin(buf[2]) < 0 || (ds3232->rtc->irq_data & RTC_UF) ? | ||
297 | 0x80 : buf[2]; | ||
298 | buf[3] = bcd2bin(buf[3]) < 0 || (ds3232->rtc->irq_data & RTC_UF) ? | ||
299 | 0x80 : buf[3]; | ||
300 | |||
301 | ret = i2c_smbus_write_i2c_block_data(client, DS3232_REG_ALARM1, 4, buf); | ||
302 | if (ret < 0) | ||
303 | goto unlock; | ||
304 | |||
305 | control = i2c_smbus_read_byte_data(client, DS3232_REG_CR); | ||
306 | if (control < 0) | ||
307 | goto unlock; | ||
308 | |||
309 | if (ds3232->rtc->irq_data & (RTC_AF | RTC_UF)) | ||
310 | /* enable alarm1 interrupt */ | ||
311 | control |= DS3232_REG_CR_A1IE; | ||
312 | else | ||
313 | /* disable alarm1 interrupt */ | ||
314 | control &= ~(DS3232_REG_CR_A1IE); | ||
315 | i2c_smbus_write_byte_data(client, DS3232_REG_CR, control); | ||
316 | |||
317 | unlock: | ||
318 | mutex_unlock(&ds3232->mutex); | ||
319 | } | ||
320 | |||
321 | static int ds3232_alarm_irq_enable(struct device *dev, unsigned int enabled) | ||
322 | { | ||
323 | struct i2c_client *client = to_i2c_client(dev); | ||
324 | struct ds3232 *ds3232 = i2c_get_clientdata(client); | ||
325 | |||
326 | if (client->irq <= 0) | ||
327 | return -EINVAL; | ||
328 | |||
329 | if (enabled) | ||
330 | ds3232->rtc->irq_data |= RTC_AF; | ||
331 | else | ||
332 | ds3232->rtc->irq_data &= ~RTC_AF; | ||
333 | |||
334 | ds3232_update_alarm(client); | ||
335 | return 0; | ||
336 | } | ||
337 | |||
338 | static int ds3232_update_irq_enable(struct device *dev, unsigned int enabled) | ||
339 | { | ||
340 | struct i2c_client *client = to_i2c_client(dev); | ||
341 | struct ds3232 *ds3232 = i2c_get_clientdata(client); | ||
342 | |||
343 | if (client->irq <= 0) | ||
344 | return -EINVAL; | ||
345 | |||
346 | if (enabled) | ||
347 | ds3232->rtc->irq_data |= RTC_UF; | ||
348 | else | ||
349 | ds3232->rtc->irq_data &= ~RTC_UF; | ||
350 | |||
351 | ds3232_update_alarm(client); | ||
352 | return 0; | ||
353 | } | ||
354 | |||
355 | static irqreturn_t ds3232_irq(int irq, void *dev_id) | ||
356 | { | ||
357 | struct i2c_client *client = dev_id; | ||
358 | struct ds3232 *ds3232 = i2c_get_clientdata(client); | ||
359 | |||
360 | disable_irq_nosync(irq); | ||
361 | schedule_work(&ds3232->work); | ||
362 | return IRQ_HANDLED; | ||
363 | } | ||
364 | |||
365 | static void ds3232_work(struct work_struct *work) | ||
366 | { | ||
367 | struct ds3232 *ds3232 = container_of(work, struct ds3232, work); | ||
368 | struct i2c_client *client = ds3232->client; | ||
369 | int stat, control; | ||
370 | |||
371 | mutex_lock(&ds3232->mutex); | ||
372 | |||
373 | stat = i2c_smbus_read_byte_data(client, DS3232_REG_SR); | ||
374 | if (stat < 0) | ||
375 | goto unlock; | ||
376 | |||
377 | if (stat & DS3232_REG_SR_A1F) { | ||
378 | control = i2c_smbus_read_byte_data(client, DS3232_REG_CR); | ||
379 | if (control < 0) | ||
380 | goto out; | ||
381 | /* disable alarm1 interrupt */ | ||
382 | control &= ~(DS3232_REG_CR_A1IE); | ||
383 | i2c_smbus_write_byte_data(client, DS3232_REG_CR, control); | ||
384 | |||
385 | /* clear the alarm pend flag */ | ||
386 | stat &= ~DS3232_REG_SR_A1F; | ||
387 | i2c_smbus_write_byte_data(client, DS3232_REG_SR, stat); | ||
388 | |||
389 | rtc_update_irq(ds3232->rtc, 1, RTC_AF | RTC_IRQF); | ||
390 | } | ||
391 | |||
392 | out: | ||
393 | if (!ds3232->exiting) | ||
394 | enable_irq(client->irq); | ||
395 | unlock: | ||
396 | mutex_unlock(&ds3232->mutex); | ||
397 | } | ||
398 | |||
399 | static const struct rtc_class_ops ds3232_rtc_ops = { | ||
400 | .read_time = ds3232_read_time, | ||
401 | .set_time = ds3232_set_time, | ||
402 | .read_alarm = ds3232_read_alarm, | ||
403 | .set_alarm = ds3232_set_alarm, | ||
404 | .alarm_irq_enable = ds3232_alarm_irq_enable, | ||
405 | .update_irq_enable = ds3232_update_irq_enable, | ||
406 | }; | ||
407 | |||
408 | static int __devinit ds3232_probe(struct i2c_client *client, | ||
409 | const struct i2c_device_id *id) | ||
410 | { | ||
411 | struct ds3232 *ds3232; | ||
412 | int ret; | ||
413 | |||
414 | ds3232 = kzalloc(sizeof(struct ds3232), GFP_KERNEL); | ||
415 | if (!ds3232) | ||
416 | return -ENOMEM; | ||
417 | |||
418 | ds3232->client = client; | ||
419 | i2c_set_clientdata(client, ds3232); | ||
420 | |||
421 | INIT_WORK(&ds3232->work, ds3232_work); | ||
422 | mutex_init(&ds3232->mutex); | ||
423 | |||
424 | ret = ds3232_check_rtc_status(client); | ||
425 | if (ret) | ||
426 | goto out_free; | ||
427 | |||
428 | ds3232->rtc = rtc_device_register(client->name, &client->dev, | ||
429 | &ds3232_rtc_ops, THIS_MODULE); | ||
430 | if (IS_ERR(ds3232->rtc)) { | ||
431 | ret = PTR_ERR(ds3232->rtc); | ||
432 | dev_err(&client->dev, "unable to register the class device\n"); | ||
433 | goto out_irq; | ||
434 | } | ||
435 | |||
436 | if (client->irq >= 0) { | ||
437 | ret = request_irq(client->irq, ds3232_irq, 0, | ||
438 | "ds3232", client); | ||
439 | if (ret) { | ||
440 | dev_err(&client->dev, "unable to request IRQ\n"); | ||
441 | goto out_free; | ||
442 | } | ||
443 | } | ||
444 | |||
445 | return 0; | ||
446 | |||
447 | out_irq: | ||
448 | if (client->irq >= 0) | ||
449 | free_irq(client->irq, client); | ||
450 | |||
451 | out_free: | ||
452 | kfree(ds3232); | ||
453 | return ret; | ||
454 | } | ||
455 | |||
456 | static int __devexit ds3232_remove(struct i2c_client *client) | ||
457 | { | ||
458 | struct ds3232 *ds3232 = i2c_get_clientdata(client); | ||
459 | |||
460 | if (client->irq >= 0) { | ||
461 | mutex_lock(&ds3232->mutex); | ||
462 | ds3232->exiting = 1; | ||
463 | mutex_unlock(&ds3232->mutex); | ||
464 | |||
465 | free_irq(client->irq, client); | ||
466 | cancel_work_sync(&ds3232->work); | ||
467 | } | ||
468 | |||
469 | rtc_device_unregister(ds3232->rtc); | ||
470 | kfree(ds3232); | ||
471 | return 0; | ||
472 | } | ||
473 | |||
474 | static const struct i2c_device_id ds3232_id[] = { | ||
475 | { "ds3232", 0 }, | ||
476 | { } | ||
477 | }; | ||
478 | MODULE_DEVICE_TABLE(i2c, ds3232_id); | ||
479 | |||
480 | static struct i2c_driver ds3232_driver = { | ||
481 | .driver = { | ||
482 | .name = "rtc-ds3232", | ||
483 | .owner = THIS_MODULE, | ||
484 | }, | ||
485 | .probe = ds3232_probe, | ||
486 | .remove = __devexit_p(ds3232_remove), | ||
487 | .id_table = ds3232_id, | ||
488 | }; | ||
489 | |||
490 | static int __init ds3232_init(void) | ||
491 | { | ||
492 | return i2c_add_driver(&ds3232_driver); | ||
493 | } | ||
494 | |||
495 | static void __exit ds3232_exit(void) | ||
496 | { | ||
497 | i2c_del_driver(&ds3232_driver); | ||
498 | } | ||
499 | |||
500 | module_init(ds3232_init); | ||
501 | module_exit(ds3232_exit); | ||
502 | |||
503 | MODULE_AUTHOR("Srikanth Srinivasan <srikanth.srinivasan@freescale.com>"); | ||
504 | MODULE_DESCRIPTION("Maxim/Dallas DS3232 RTC Driver"); | ||
505 | MODULE_LICENSE("GPL"); | ||