diff options
Diffstat (limited to 'drivers/rtc/rtc-m48t86.c')
-rw-r--r-- | drivers/rtc/rtc-m48t86.c | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/drivers/rtc/rtc-m48t86.c b/drivers/rtc/rtc-m48t86.c new file mode 100644 index 000000000000..db445c872b1b --- /dev/null +++ b/drivers/rtc/rtc-m48t86.c | |||
@@ -0,0 +1,209 @@ | |||
1 | /* | ||
2 | * ST M48T86 / Dallas DS12887 RTC driver | ||
3 | * Copyright (c) 2006 Tower Technologies | ||
4 | * | ||
5 | * Author: Alessandro Zummo <a.zummo@towertech.it> | ||
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 | * This drivers only supports the clock running in BCD and 24H mode. | ||
12 | * If it will be ever adapted to binary and 12H mode, care must be taken | ||
13 | * to not introduce bugs. | ||
14 | */ | ||
15 | |||
16 | #include <linux/module.h> | ||
17 | #include <linux/rtc.h> | ||
18 | #include <linux/platform_device.h> | ||
19 | #include <linux/m48t86.h> | ||
20 | #include <linux/bcd.h> | ||
21 | |||
22 | #define M48T86_REG_SEC 0x00 | ||
23 | #define M48T86_REG_SECALRM 0x01 | ||
24 | #define M48T86_REG_MIN 0x02 | ||
25 | #define M48T86_REG_MINALRM 0x03 | ||
26 | #define M48T86_REG_HOUR 0x04 | ||
27 | #define M48T86_REG_HOURALRM 0x05 | ||
28 | #define M48T86_REG_DOW 0x06 /* 1 = sunday */ | ||
29 | #define M48T86_REG_DOM 0x07 | ||
30 | #define M48T86_REG_MONTH 0x08 /* 1 - 12 */ | ||
31 | #define M48T86_REG_YEAR 0x09 /* 0 - 99 */ | ||
32 | #define M48T86_REG_A 0x0A | ||
33 | #define M48T86_REG_B 0x0B | ||
34 | #define M48T86_REG_C 0x0C | ||
35 | #define M48T86_REG_D 0x0D | ||
36 | |||
37 | #define M48T86_REG_B_H24 (1 << 1) | ||
38 | #define M48T86_REG_B_DM (1 << 2) | ||
39 | #define M48T86_REG_B_SET (1 << 7) | ||
40 | #define M48T86_REG_D_VRT (1 << 7) | ||
41 | |||
42 | #define DRV_VERSION "0.1" | ||
43 | |||
44 | |||
45 | static int m48t86_rtc_read_time(struct device *dev, struct rtc_time *tm) | ||
46 | { | ||
47 | unsigned char reg; | ||
48 | struct platform_device *pdev = to_platform_device(dev); | ||
49 | struct m48t86_ops *ops = pdev->dev.platform_data; | ||
50 | |||
51 | reg = ops->readb(M48T86_REG_B); | ||
52 | |||
53 | if (reg & M48T86_REG_B_DM) { | ||
54 | /* data (binary) mode */ | ||
55 | tm->tm_sec = ops->readb(M48T86_REG_SEC); | ||
56 | tm->tm_min = ops->readb(M48T86_REG_MIN); | ||
57 | tm->tm_hour = ops->readb(M48T86_REG_HOUR) & 0x3F; | ||
58 | tm->tm_mday = ops->readb(M48T86_REG_DOM); | ||
59 | /* tm_mon is 0-11 */ | ||
60 | tm->tm_mon = ops->readb(M48T86_REG_MONTH) - 1; | ||
61 | tm->tm_year = ops->readb(M48T86_REG_YEAR) + 100; | ||
62 | tm->tm_wday = ops->readb(M48T86_REG_DOW); | ||
63 | } else { | ||
64 | /* bcd mode */ | ||
65 | tm->tm_sec = BCD2BIN(ops->readb(M48T86_REG_SEC)); | ||
66 | tm->tm_min = BCD2BIN(ops->readb(M48T86_REG_MIN)); | ||
67 | tm->tm_hour = BCD2BIN(ops->readb(M48T86_REG_HOUR) & 0x3F); | ||
68 | tm->tm_mday = BCD2BIN(ops->readb(M48T86_REG_DOM)); | ||
69 | /* tm_mon is 0-11 */ | ||
70 | tm->tm_mon = BCD2BIN(ops->readb(M48T86_REG_MONTH)) - 1; | ||
71 | tm->tm_year = BCD2BIN(ops->readb(M48T86_REG_YEAR)) + 100; | ||
72 | tm->tm_wday = BCD2BIN(ops->readb(M48T86_REG_DOW)); | ||
73 | } | ||
74 | |||
75 | /* correct the hour if the clock is in 12h mode */ | ||
76 | if (!(reg & M48T86_REG_B_H24)) | ||
77 | if (ops->readb(M48T86_REG_HOUR) & 0x80) | ||
78 | tm->tm_hour += 12; | ||
79 | |||
80 | return 0; | ||
81 | } | ||
82 | |||
83 | static int m48t86_rtc_set_time(struct device *dev, struct rtc_time *tm) | ||
84 | { | ||
85 | unsigned char reg; | ||
86 | struct platform_device *pdev = to_platform_device(dev); | ||
87 | struct m48t86_ops *ops = pdev->dev.platform_data; | ||
88 | |||
89 | reg = ops->readb(M48T86_REG_B); | ||
90 | |||
91 | /* update flag and 24h mode */ | ||
92 | reg |= M48T86_REG_B_SET | M48T86_REG_B_H24; | ||
93 | ops->writeb(reg, M48T86_REG_B); | ||
94 | |||
95 | if (reg & M48T86_REG_B_DM) { | ||
96 | /* data (binary) mode */ | ||
97 | ops->writeb(tm->tm_sec, M48T86_REG_SEC); | ||
98 | ops->writeb(tm->tm_min, M48T86_REG_MIN); | ||
99 | ops->writeb(tm->tm_hour, M48T86_REG_HOUR); | ||
100 | ops->writeb(tm->tm_mday, M48T86_REG_DOM); | ||
101 | ops->writeb(tm->tm_mon + 1, M48T86_REG_MONTH); | ||
102 | ops->writeb(tm->tm_year % 100, M48T86_REG_YEAR); | ||
103 | ops->writeb(tm->tm_wday, M48T86_REG_DOW); | ||
104 | } else { | ||
105 | /* bcd mode */ | ||
106 | ops->writeb(BIN2BCD(tm->tm_sec), M48T86_REG_SEC); | ||
107 | ops->writeb(BIN2BCD(tm->tm_min), M48T86_REG_MIN); | ||
108 | ops->writeb(BIN2BCD(tm->tm_hour), M48T86_REG_HOUR); | ||
109 | ops->writeb(BIN2BCD(tm->tm_mday), M48T86_REG_DOM); | ||
110 | ops->writeb(BIN2BCD(tm->tm_mon + 1), M48T86_REG_MONTH); | ||
111 | ops->writeb(BIN2BCD(tm->tm_year % 100), M48T86_REG_YEAR); | ||
112 | ops->writeb(BIN2BCD(tm->tm_wday), M48T86_REG_DOW); | ||
113 | } | ||
114 | |||
115 | /* update ended */ | ||
116 | reg &= ~M48T86_REG_B_SET; | ||
117 | ops->writeb(reg, M48T86_REG_B); | ||
118 | |||
119 | return 0; | ||
120 | } | ||
121 | |||
122 | static int m48t86_rtc_proc(struct device *dev, struct seq_file *seq) | ||
123 | { | ||
124 | unsigned char reg; | ||
125 | struct platform_device *pdev = to_platform_device(dev); | ||
126 | struct m48t86_ops *ops = pdev->dev.platform_data; | ||
127 | |||
128 | reg = ops->readb(M48T86_REG_B); | ||
129 | |||
130 | seq_printf(seq, "24hr\t\t: %s\n", | ||
131 | (reg & M48T86_REG_B_H24) ? "yes" : "no"); | ||
132 | |||
133 | seq_printf(seq, "mode\t\t: %s\n", | ||
134 | (reg & M48T86_REG_B_DM) ? "binary" : "bcd"); | ||
135 | |||
136 | reg = ops->readb(M48T86_REG_D); | ||
137 | |||
138 | seq_printf(seq, "battery\t\t: %s\n", | ||
139 | (reg & M48T86_REG_D_VRT) ? "ok" : "exhausted"); | ||
140 | |||
141 | return 0; | ||
142 | } | ||
143 | |||
144 | static struct rtc_class_ops m48t86_rtc_ops = { | ||
145 | .read_time = m48t86_rtc_read_time, | ||
146 | .set_time = m48t86_rtc_set_time, | ||
147 | .proc = m48t86_rtc_proc, | ||
148 | }; | ||
149 | |||
150 | static int __devinit m48t86_rtc_probe(struct platform_device *dev) | ||
151 | { | ||
152 | unsigned char reg; | ||
153 | struct m48t86_ops *ops = dev->dev.platform_data; | ||
154 | struct rtc_device *rtc = rtc_device_register("m48t86", | ||
155 | &dev->dev, &m48t86_rtc_ops, THIS_MODULE); | ||
156 | |||
157 | if (IS_ERR(rtc)) { | ||
158 | dev_err(&dev->dev, "unable to register\n"); | ||
159 | return PTR_ERR(rtc); | ||
160 | } | ||
161 | |||
162 | platform_set_drvdata(dev, rtc); | ||
163 | |||
164 | /* read battery status */ | ||
165 | reg = ops->readb(M48T86_REG_D); | ||
166 | dev_info(&dev->dev, "battery %s\n", | ||
167 | (reg & M48T86_REG_D_VRT) ? "ok" : "exhausted"); | ||
168 | |||
169 | return 0; | ||
170 | } | ||
171 | |||
172 | static int __devexit m48t86_rtc_remove(struct platform_device *dev) | ||
173 | { | ||
174 | struct rtc_device *rtc = platform_get_drvdata(dev); | ||
175 | |||
176 | if (rtc) | ||
177 | rtc_device_unregister(rtc); | ||
178 | |||
179 | platform_set_drvdata(dev, NULL); | ||
180 | |||
181 | return 0; | ||
182 | } | ||
183 | |||
184 | static struct platform_driver m48t86_rtc_platform_driver = { | ||
185 | .driver = { | ||
186 | .name = "rtc-m48t86", | ||
187 | .owner = THIS_MODULE, | ||
188 | }, | ||
189 | .probe = m48t86_rtc_probe, | ||
190 | .remove = __devexit_p(m48t86_rtc_remove), | ||
191 | }; | ||
192 | |||
193 | static int __init m48t86_rtc_init(void) | ||
194 | { | ||
195 | return platform_driver_register(&m48t86_rtc_platform_driver); | ||
196 | } | ||
197 | |||
198 | static void __exit m48t86_rtc_exit(void) | ||
199 | { | ||
200 | platform_driver_unregister(&m48t86_rtc_platform_driver); | ||
201 | } | ||
202 | |||
203 | MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>"); | ||
204 | MODULE_DESCRIPTION("M48T86 RTC driver"); | ||
205 | MODULE_LICENSE("GPL"); | ||
206 | MODULE_VERSION(DRV_VERSION); | ||
207 | |||
208 | module_init(m48t86_rtc_init); | ||
209 | module_exit(m48t86_rtc_exit); | ||