diff options
author | Atsushi Nemoto <anemo@mba.ocn.ne.jp> | 2007-07-17 07:05:02 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-07-17 13:23:09 -0400 |
commit | caaff562e0ba44a7991ee8322fa4a6891d939757 (patch) | |
tree | 64be9b44b48c450613e395a7cc2c462f82cfc77a /drivers/rtc/rtc-m41t80.c | |
parent | 5663c14b4f3e22aece38970f9765ceb090efbb8c (diff) |
rtc: add rtc-m41t80 driver
This is a new-style i2c driver for ST M41T80 series RTC chip, derived from
works by Alexander Bigga <ab@mycable.de> who wrote the original
rtc-m41txx.c based on drivers/i2c/chips/m41t00.c driver.
This driver supports M41T8[0-4] and M41ST8[457]. The old m41t00 driver
supports M41T00, M41T81 and M41T85(M41ST85). While the M41T00 chip is now
supported by rtc-ds1307 driver, this driver does not include support for
the chip.
[akpm@linux-foundation.org: remove bogus `static']
Signed-off-by: Atsushi Nemoto <anemo@mba.ocn.ne.jp>
Signed-off-by: Alexander Bigga <ab@mycable.de>
Acked-by: Mark A. Greer <mgreer@mvista.com>
Cc: David Brownell <david-b@pacbell.net>
Acked-by: Alessandro Zummo <a.zummo@towertech.it>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/rtc/rtc-m41t80.c')
-rw-r--r-- | drivers/rtc/rtc-m41t80.c | 629 |
1 files changed, 629 insertions, 0 deletions
diff --git a/drivers/rtc/rtc-m41t80.c b/drivers/rtc/rtc-m41t80.c new file mode 100644 index 000000000000..4875a4444216 --- /dev/null +++ b/drivers/rtc/rtc-m41t80.c | |||
@@ -0,0 +1,629 @@ | |||
1 | /* | ||
2 | * I2C client/driver for the ST M41T80 family of i2c rtc chips. | ||
3 | * | ||
4 | * Author: Alexander Bigga <ab@mycable.de> | ||
5 | * | ||
6 | * Based on m41t00.c by Mark A. Greer <mgreer@mvista.com> | ||
7 | * | ||
8 | * 2006 (c) mycable GmbH | ||
9 | * | ||
10 | * This program is free software; you can redistribute it and/or modify | ||
11 | * it under the terms of the GNU General Public License version 2 as | ||
12 | * published by the Free Software Foundation. | ||
13 | * | ||
14 | */ | ||
15 | |||
16 | #include <linux/module.h> | ||
17 | #include <linux/init.h> | ||
18 | #include <linux/slab.h> | ||
19 | #include <linux/string.h> | ||
20 | #include <linux/i2c.h> | ||
21 | #include <linux/rtc.h> | ||
22 | #include <linux/bcd.h> | ||
23 | |||
24 | #define M41T80_REG_SSEC 0 | ||
25 | #define M41T80_REG_SEC 1 | ||
26 | #define M41T80_REG_MIN 2 | ||
27 | #define M41T80_REG_HOUR 3 | ||
28 | #define M41T80_REG_WDAY 4 | ||
29 | #define M41T80_REG_DAY 5 | ||
30 | #define M41T80_REG_MON 6 | ||
31 | #define M41T80_REG_YEAR 7 | ||
32 | #define M41T80_REG_ALARM_MON 0xa | ||
33 | #define M41T80_REG_ALARM_DAY 0xb | ||
34 | #define M41T80_REG_ALARM_HOUR 0xc | ||
35 | #define M41T80_REG_ALARM_MIN 0xd | ||
36 | #define M41T80_REG_ALARM_SEC 0xe | ||
37 | #define M41T80_REG_FLAGS 0xf | ||
38 | #define M41T80_REG_SQW 0x13 | ||
39 | |||
40 | #define M41T80_DATETIME_REG_SIZE (M41T80_REG_YEAR + 1) | ||
41 | #define M41T80_ALARM_REG_SIZE \ | ||
42 | (M41T80_REG_ALARM_SEC + 1 - M41T80_REG_ALARM_MON) | ||
43 | |||
44 | #define M41T80_SEC_ST (1 << 7) /* ST: Stop Bit */ | ||
45 | #define M41T80_ALMON_AFE (1 << 7) /* AFE: AF Enable Bit */ | ||
46 | #define M41T80_ALMON_SQWE (1 << 6) /* SQWE: SQW Enable Bit */ | ||
47 | #define M41T80_ALHOUR_HT (1 << 6) /* HT: Halt Update Bit */ | ||
48 | #define M41T80_FLAGS_AF (1 << 6) /* AF: Alarm Flag Bit */ | ||
49 | #define M41T80_FLAGS_BATT_LOW (1 << 4) /* BL: Battery Low Bit */ | ||
50 | |||
51 | #define M41T80_FEATURE_HT (1 << 0) | ||
52 | #define M41T80_FEATURE_BL (1 << 1) | ||
53 | |||
54 | #define DRV_VERSION "0.05" | ||
55 | |||
56 | struct m41t80_chip_info { | ||
57 | const char *name; | ||
58 | u8 features; | ||
59 | }; | ||
60 | |||
61 | static const struct m41t80_chip_info m41t80_chip_info_tbl[] = { | ||
62 | { | ||
63 | .name = "m41t80", | ||
64 | .features = 0, | ||
65 | }, | ||
66 | { | ||
67 | .name = "m41t81", | ||
68 | .features = M41T80_FEATURE_HT, | ||
69 | }, | ||
70 | { | ||
71 | .name = "m41t81s", | ||
72 | .features = M41T80_FEATURE_HT | M41T80_FEATURE_BL, | ||
73 | }, | ||
74 | { | ||
75 | .name = "m41t82", | ||
76 | .features = M41T80_FEATURE_HT | M41T80_FEATURE_BL, | ||
77 | }, | ||
78 | { | ||
79 | .name = "m41t83", | ||
80 | .features = M41T80_FEATURE_HT | M41T80_FEATURE_BL, | ||
81 | }, | ||
82 | { | ||
83 | .name = "m41st84", | ||
84 | .features = M41T80_FEATURE_HT | M41T80_FEATURE_BL, | ||
85 | }, | ||
86 | { | ||
87 | .name = "m41st85", | ||
88 | .features = M41T80_FEATURE_HT | M41T80_FEATURE_BL, | ||
89 | }, | ||
90 | { | ||
91 | .name = "m41st87", | ||
92 | .features = M41T80_FEATURE_HT | M41T80_FEATURE_BL, | ||
93 | }, | ||
94 | }; | ||
95 | |||
96 | struct m41t80_data { | ||
97 | const struct m41t80_chip_info *chip; | ||
98 | struct rtc_device *rtc; | ||
99 | }; | ||
100 | |||
101 | static int m41t80_get_datetime(struct i2c_client *client, | ||
102 | struct rtc_time *tm) | ||
103 | { | ||
104 | u8 buf[M41T80_DATETIME_REG_SIZE], dt_addr[1] = { M41T80_REG_SEC }; | ||
105 | struct i2c_msg msgs[] = { | ||
106 | { | ||
107 | .addr = client->addr, | ||
108 | .flags = 0, | ||
109 | .len = 1, | ||
110 | .buf = dt_addr, | ||
111 | }, | ||
112 | { | ||
113 | .addr = client->addr, | ||
114 | .flags = I2C_M_RD, | ||
115 | .len = M41T80_DATETIME_REG_SIZE - M41T80_REG_SEC, | ||
116 | .buf = buf + M41T80_REG_SEC, | ||
117 | }, | ||
118 | }; | ||
119 | |||
120 | if (i2c_transfer(client->adapter, msgs, 2) < 0) { | ||
121 | dev_err(&client->dev, "read error\n"); | ||
122 | return -EIO; | ||
123 | } | ||
124 | |||
125 | tm->tm_sec = BCD2BIN(buf[M41T80_REG_SEC] & 0x7f); | ||
126 | tm->tm_min = BCD2BIN(buf[M41T80_REG_MIN] & 0x7f); | ||
127 | tm->tm_hour = BCD2BIN(buf[M41T80_REG_HOUR] & 0x3f); | ||
128 | tm->tm_mday = BCD2BIN(buf[M41T80_REG_DAY] & 0x3f); | ||
129 | tm->tm_wday = buf[M41T80_REG_WDAY] & 0x07; | ||
130 | tm->tm_mon = BCD2BIN(buf[M41T80_REG_MON] & 0x1f) - 1; | ||
131 | |||
132 | /* assume 20YY not 19YY, and ignore the Century Bit */ | ||
133 | tm->tm_year = BCD2BIN(buf[M41T80_REG_YEAR]) + 100; | ||
134 | return 0; | ||
135 | } | ||
136 | |||
137 | /* Sets the given date and time to the real time clock. */ | ||
138 | static int m41t80_set_datetime(struct i2c_client *client, struct rtc_time *tm) | ||
139 | { | ||
140 | u8 wbuf[1 + M41T80_DATETIME_REG_SIZE]; | ||
141 | u8 *buf = &wbuf[1]; | ||
142 | u8 dt_addr[1] = { M41T80_REG_SEC }; | ||
143 | struct i2c_msg msgs_in[] = { | ||
144 | { | ||
145 | .addr = client->addr, | ||
146 | .flags = 0, | ||
147 | .len = 1, | ||
148 | .buf = dt_addr, | ||
149 | }, | ||
150 | { | ||
151 | .addr = client->addr, | ||
152 | .flags = I2C_M_RD, | ||
153 | .len = M41T80_DATETIME_REG_SIZE - M41T80_REG_SEC, | ||
154 | .buf = buf + M41T80_REG_SEC, | ||
155 | }, | ||
156 | }; | ||
157 | struct i2c_msg msgs[] = { | ||
158 | { | ||
159 | .addr = client->addr, | ||
160 | .flags = 0, | ||
161 | .len = 1 + M41T80_DATETIME_REG_SIZE, | ||
162 | .buf = wbuf, | ||
163 | }, | ||
164 | }; | ||
165 | |||
166 | /* Read current reg values into buf[1..7] */ | ||
167 | if (i2c_transfer(client->adapter, msgs_in, 2) < 0) { | ||
168 | dev_err(&client->dev, "read error\n"); | ||
169 | return -EIO; | ||
170 | } | ||
171 | |||
172 | wbuf[0] = 0; /* offset into rtc's regs */ | ||
173 | /* Merge time-data and register flags into buf[0..7] */ | ||
174 | buf[M41T80_REG_SSEC] = 0; | ||
175 | buf[M41T80_REG_SEC] = | ||
176 | BIN2BCD(tm->tm_sec) | (buf[M41T80_REG_SEC] & ~0x7f); | ||
177 | buf[M41T80_REG_MIN] = | ||
178 | BIN2BCD(tm->tm_min) | (buf[M41T80_REG_MIN] & ~0x7f); | ||
179 | buf[M41T80_REG_HOUR] = | ||
180 | BIN2BCD(tm->tm_hour) | (buf[M41T80_REG_HOUR] & ~0x3f) ; | ||
181 | buf[M41T80_REG_WDAY] = | ||
182 | (tm->tm_wday & 0x07) | (buf[M41T80_REG_WDAY] & ~0x07); | ||
183 | buf[M41T80_REG_DAY] = | ||
184 | BIN2BCD(tm->tm_mday) | (buf[M41T80_REG_DAY] & ~0x3f); | ||
185 | buf[M41T80_REG_MON] = | ||
186 | BIN2BCD(tm->tm_mon + 1) | (buf[M41T80_REG_MON] & ~0x1f); | ||
187 | /* assume 20YY not 19YY */ | ||
188 | buf[M41T80_REG_YEAR] = BIN2BCD(tm->tm_year % 100); | ||
189 | |||
190 | if (i2c_transfer(client->adapter, msgs, 1) != 1) { | ||
191 | dev_err(&client->dev, "write error\n"); | ||
192 | return -EIO; | ||
193 | } | ||
194 | return 0; | ||
195 | } | ||
196 | |||
197 | #if defined(CONFIG_RTC_INTF_PROC) || defined(CONFIG_RTC_INTF_PROC_MODULE) | ||
198 | static int m41t80_rtc_proc(struct device *dev, struct seq_file *seq) | ||
199 | { | ||
200 | struct i2c_client *client = to_i2c_client(dev); | ||
201 | struct m41t80_data *clientdata = i2c_get_clientdata(client); | ||
202 | u8 reg; | ||
203 | |||
204 | if (clientdata->chip->features & M41T80_FEATURE_BL) { | ||
205 | reg = i2c_smbus_read_byte_data(client, M41T80_REG_FLAGS); | ||
206 | seq_printf(seq, "battery\t\t: %s\n", | ||
207 | (reg & M41T80_FLAGS_BATT_LOW) ? "exhausted" : "ok"); | ||
208 | } | ||
209 | return 0; | ||
210 | } | ||
211 | #else | ||
212 | #define m41t80_rtc_proc NULL | ||
213 | #endif | ||
214 | |||
215 | static int m41t80_rtc_read_time(struct device *dev, struct rtc_time *tm) | ||
216 | { | ||
217 | return m41t80_get_datetime(to_i2c_client(dev), tm); | ||
218 | } | ||
219 | |||
220 | static int m41t80_rtc_set_time(struct device *dev, struct rtc_time *tm) | ||
221 | { | ||
222 | return m41t80_set_datetime(to_i2c_client(dev), tm); | ||
223 | } | ||
224 | |||
225 | #if defined(CONFIG_RTC_INTF_DEV) || defined(CONFIG_RTC_INTF_DEV_MODULE) | ||
226 | static int | ||
227 | m41t80_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) | ||
228 | { | ||
229 | struct i2c_client *client = to_i2c_client(dev); | ||
230 | int rc; | ||
231 | |||
232 | switch (cmd) { | ||
233 | case RTC_AIE_OFF: | ||
234 | case RTC_AIE_ON: | ||
235 | break; | ||
236 | default: | ||
237 | return -ENOIOCTLCMD; | ||
238 | } | ||
239 | |||
240 | rc = i2c_smbus_read_byte_data(client, M41T80_REG_ALARM_MON); | ||
241 | if (rc < 0) | ||
242 | goto err; | ||
243 | switch (cmd) { | ||
244 | case RTC_AIE_OFF: | ||
245 | rc &= ~M41T80_ALMON_AFE; | ||
246 | break; | ||
247 | case RTC_AIE_ON: | ||
248 | rc |= M41T80_ALMON_AFE; | ||
249 | break; | ||
250 | } | ||
251 | if (i2c_smbus_write_byte_data(client, M41T80_REG_ALARM_MON, rc) < 0) | ||
252 | goto err; | ||
253 | return 0; | ||
254 | err: | ||
255 | return -EIO; | ||
256 | } | ||
257 | #else | ||
258 | #define m41t80_rtc_ioctl NULL | ||
259 | #endif | ||
260 | |||
261 | static int m41t80_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *t) | ||
262 | { | ||
263 | struct i2c_client *client = to_i2c_client(dev); | ||
264 | u8 wbuf[1 + M41T80_ALARM_REG_SIZE]; | ||
265 | u8 *buf = &wbuf[1]; | ||
266 | u8 *reg = buf - M41T80_REG_ALARM_MON; | ||
267 | u8 dt_addr[1] = { M41T80_REG_ALARM_MON }; | ||
268 | struct i2c_msg msgs_in[] = { | ||
269 | { | ||
270 | .addr = client->addr, | ||
271 | .flags = 0, | ||
272 | .len = 1, | ||
273 | .buf = dt_addr, | ||
274 | }, | ||
275 | { | ||
276 | .addr = client->addr, | ||
277 | .flags = I2C_M_RD, | ||
278 | .len = M41T80_ALARM_REG_SIZE, | ||
279 | .buf = buf, | ||
280 | }, | ||
281 | }; | ||
282 | struct i2c_msg msgs[] = { | ||
283 | { | ||
284 | .addr = client->addr, | ||
285 | .flags = 0, | ||
286 | .len = 1 + M41T80_ALARM_REG_SIZE, | ||
287 | .buf = wbuf, | ||
288 | }, | ||
289 | }; | ||
290 | |||
291 | if (i2c_transfer(client->adapter, msgs_in, 2) < 0) { | ||
292 | dev_err(&client->dev, "read error\n"); | ||
293 | return -EIO; | ||
294 | } | ||
295 | reg[M41T80_REG_ALARM_MON] &= ~(0x1f | M41T80_ALMON_AFE); | ||
296 | reg[M41T80_REG_ALARM_DAY] = 0; | ||
297 | reg[M41T80_REG_ALARM_HOUR] &= ~(0x3f | 0x80); | ||
298 | reg[M41T80_REG_ALARM_MIN] = 0; | ||
299 | reg[M41T80_REG_ALARM_SEC] = 0; | ||
300 | |||
301 | wbuf[0] = M41T80_REG_ALARM_MON; /* offset into rtc's regs */ | ||
302 | reg[M41T80_REG_ALARM_SEC] |= t->time.tm_sec >= 0 ? | ||
303 | BIN2BCD(t->time.tm_sec) : 0x80; | ||
304 | reg[M41T80_REG_ALARM_MIN] |= t->time.tm_min >= 0 ? | ||
305 | BIN2BCD(t->time.tm_min) : 0x80; | ||
306 | reg[M41T80_REG_ALARM_HOUR] |= t->time.tm_hour >= 0 ? | ||
307 | BIN2BCD(t->time.tm_hour) : 0x80; | ||
308 | reg[M41T80_REG_ALARM_DAY] |= t->time.tm_mday >= 0 ? | ||
309 | BIN2BCD(t->time.tm_mday) : 0x80; | ||
310 | if (t->time.tm_mon >= 0) | ||
311 | reg[M41T80_REG_ALARM_MON] |= BIN2BCD(t->time.tm_mon + 1); | ||
312 | else | ||
313 | reg[M41T80_REG_ALARM_DAY] |= 0x40; | ||
314 | |||
315 | if (i2c_transfer(client->adapter, msgs, 1) != 1) { | ||
316 | dev_err(&client->dev, "write error\n"); | ||
317 | return -EIO; | ||
318 | } | ||
319 | |||
320 | if (t->enabled) { | ||
321 | reg[M41T80_REG_ALARM_MON] |= M41T80_ALMON_AFE; | ||
322 | if (i2c_smbus_write_byte_data(client, M41T80_REG_ALARM_MON, | ||
323 | reg[M41T80_REG_ALARM_MON]) < 0) { | ||
324 | dev_err(&client->dev, "write error\n"); | ||
325 | return -EIO; | ||
326 | } | ||
327 | } | ||
328 | return 0; | ||
329 | } | ||
330 | |||
331 | static int m41t80_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *t) | ||
332 | { | ||
333 | struct i2c_client *client = to_i2c_client(dev); | ||
334 | u8 buf[M41T80_ALARM_REG_SIZE + 1]; /* all alarm regs and flags */ | ||
335 | u8 dt_addr[1] = { M41T80_REG_ALARM_MON }; | ||
336 | u8 *reg = buf - M41T80_REG_ALARM_MON; | ||
337 | struct i2c_msg msgs[] = { | ||
338 | { | ||
339 | .addr = client->addr, | ||
340 | .flags = 0, | ||
341 | .len = 1, | ||
342 | .buf = dt_addr, | ||
343 | }, | ||
344 | { | ||
345 | .addr = client->addr, | ||
346 | .flags = I2C_M_RD, | ||
347 | .len = M41T80_ALARM_REG_SIZE + 1, | ||
348 | .buf = buf, | ||
349 | }, | ||
350 | }; | ||
351 | |||
352 | if (i2c_transfer(client->adapter, msgs, 2) < 0) { | ||
353 | dev_err(&client->dev, "read error\n"); | ||
354 | return -EIO; | ||
355 | } | ||
356 | t->time.tm_sec = -1; | ||
357 | t->time.tm_min = -1; | ||
358 | t->time.tm_hour = -1; | ||
359 | t->time.tm_mday = -1; | ||
360 | t->time.tm_mon = -1; | ||
361 | if (!(reg[M41T80_REG_ALARM_SEC] & 0x80)) | ||
362 | t->time.tm_sec = BCD2BIN(reg[M41T80_REG_ALARM_SEC] & 0x7f); | ||
363 | if (!(reg[M41T80_REG_ALARM_MIN] & 0x80)) | ||
364 | t->time.tm_min = BCD2BIN(reg[M41T80_REG_ALARM_MIN] & 0x7f); | ||
365 | if (!(reg[M41T80_REG_ALARM_HOUR] & 0x80)) | ||
366 | t->time.tm_hour = BCD2BIN(reg[M41T80_REG_ALARM_HOUR] & 0x3f); | ||
367 | if (!(reg[M41T80_REG_ALARM_DAY] & 0x80)) | ||
368 | t->time.tm_mday = BCD2BIN(reg[M41T80_REG_ALARM_DAY] & 0x3f); | ||
369 | if (!(reg[M41T80_REG_ALARM_DAY] & 0x40)) | ||
370 | t->time.tm_mon = BCD2BIN(reg[M41T80_REG_ALARM_MON] & 0x1f) - 1; | ||
371 | t->time.tm_year = -1; | ||
372 | t->time.tm_wday = -1; | ||
373 | t->time.tm_yday = -1; | ||
374 | t->time.tm_isdst = -1; | ||
375 | t->enabled = !!(reg[M41T80_REG_ALARM_MON] & M41T80_ALMON_AFE); | ||
376 | t->pending = !!(reg[M41T80_REG_FLAGS] & M41T80_FLAGS_AF); | ||
377 | return 0; | ||
378 | } | ||
379 | |||
380 | static struct rtc_class_ops m41t80_rtc_ops = { | ||
381 | .read_time = m41t80_rtc_read_time, | ||
382 | .set_time = m41t80_rtc_set_time, | ||
383 | .read_alarm = m41t80_rtc_read_alarm, | ||
384 | .set_alarm = m41t80_rtc_set_alarm, | ||
385 | .proc = m41t80_rtc_proc, | ||
386 | .ioctl = m41t80_rtc_ioctl, | ||
387 | }; | ||
388 | |||
389 | #if defined(CONFIG_RTC_INTF_SYSFS) || defined(CONFIG_RTC_INTF_SYSFS_MODULE) | ||
390 | static ssize_t m41t80_sysfs_show_flags(struct device *dev, | ||
391 | struct device_attribute *attr, char *buf) | ||
392 | { | ||
393 | struct i2c_client *client = to_i2c_client(dev); | ||
394 | int val; | ||
395 | |||
396 | val = i2c_smbus_read_byte_data(client, M41T80_REG_FLAGS); | ||
397 | if (val < 0) | ||
398 | return -EIO; | ||
399 | return sprintf(buf, "%#x\n", val); | ||
400 | } | ||
401 | static DEVICE_ATTR(flags, S_IRUGO, m41t80_sysfs_show_flags, NULL); | ||
402 | |||
403 | static ssize_t m41t80_sysfs_show_sqwfreq(struct device *dev, | ||
404 | struct device_attribute *attr, char *buf) | ||
405 | { | ||
406 | struct i2c_client *client = to_i2c_client(dev); | ||
407 | int val; | ||
408 | |||
409 | val = i2c_smbus_read_byte_data(client, M41T80_REG_SQW); | ||
410 | if (val < 0) | ||
411 | return -EIO; | ||
412 | val = (val >> 4) & 0xf; | ||
413 | switch (val) { | ||
414 | case 0: | ||
415 | break; | ||
416 | case 1: | ||
417 | val = 32768; | ||
418 | break; | ||
419 | default: | ||
420 | val = 32768 >> val; | ||
421 | } | ||
422 | return sprintf(buf, "%d\n", val); | ||
423 | } | ||
424 | static ssize_t m41t80_sysfs_set_sqwfreq(struct device *dev, | ||
425 | struct device_attribute *attr, | ||
426 | const char *buf, size_t count) | ||
427 | { | ||
428 | struct i2c_client *client = to_i2c_client(dev); | ||
429 | int almon, sqw; | ||
430 | int val = simple_strtoul(buf, NULL, 0); | ||
431 | |||
432 | if (val) { | ||
433 | if (!is_power_of_2(val)) | ||
434 | return -EINVAL; | ||
435 | val = ilog2(val); | ||
436 | if (val == 15) | ||
437 | val = 1; | ||
438 | else if (val < 14) | ||
439 | val = 15 - val; | ||
440 | else | ||
441 | return -EINVAL; | ||
442 | } | ||
443 | /* disable SQW, set SQW frequency & re-enable */ | ||
444 | almon = i2c_smbus_read_byte_data(client, M41T80_REG_ALARM_MON); | ||
445 | if (almon < 0) | ||
446 | return -EIO; | ||
447 | sqw = i2c_smbus_read_byte_data(client, M41T80_REG_SQW); | ||
448 | if (sqw < 0) | ||
449 | return -EIO; | ||
450 | sqw = (sqw & 0x0f) | (val << 4); | ||
451 | if (i2c_smbus_write_byte_data(client, M41T80_REG_ALARM_MON, | ||
452 | almon & ~M41T80_ALMON_SQWE) < 0 || | ||
453 | i2c_smbus_write_byte_data(client, M41T80_REG_SQW, sqw) < 0) | ||
454 | return -EIO; | ||
455 | if (val && i2c_smbus_write_byte_data(client, M41T80_REG_ALARM_MON, | ||
456 | almon | M41T80_ALMON_SQWE) < 0) | ||
457 | return -EIO; | ||
458 | return count; | ||
459 | } | ||
460 | static DEVICE_ATTR(sqwfreq, S_IRUGO | S_IWUSR, | ||
461 | m41t80_sysfs_show_sqwfreq, m41t80_sysfs_set_sqwfreq); | ||
462 | |||
463 | static struct attribute *attrs[] = { | ||
464 | &dev_attr_flags.attr, | ||
465 | &dev_attr_sqwfreq.attr, | ||
466 | NULL, | ||
467 | }; | ||
468 | static struct attribute_group attr_group = { | ||
469 | .attrs = attrs, | ||
470 | }; | ||
471 | |||
472 | static int m41t80_sysfs_register(struct device *dev) | ||
473 | { | ||
474 | return sysfs_create_group(&dev->kobj, &attr_group); | ||
475 | } | ||
476 | #else | ||
477 | static int m41t80_sysfs_register(struct device *dev) | ||
478 | { | ||
479 | return 0; | ||
480 | } | ||
481 | #endif | ||
482 | |||
483 | /* | ||
484 | ***************************************************************************** | ||
485 | * | ||
486 | * Driver Interface | ||
487 | * | ||
488 | ***************************************************************************** | ||
489 | */ | ||
490 | static int m41t80_probe(struct i2c_client *client) | ||
491 | { | ||
492 | int i, rc = 0; | ||
493 | struct rtc_device *rtc = NULL; | ||
494 | struct rtc_time tm; | ||
495 | const struct m41t80_chip_info *chip; | ||
496 | struct m41t80_data *clientdata = NULL; | ||
497 | |||
498 | if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C | ||
499 | | I2C_FUNC_SMBUS_BYTE_DATA)) { | ||
500 | rc = -ENODEV; | ||
501 | goto exit; | ||
502 | } | ||
503 | |||
504 | dev_info(&client->dev, | ||
505 | "chip found, driver version " DRV_VERSION "\n"); | ||
506 | |||
507 | chip = NULL; | ||
508 | for (i = 0; i < ARRAY_SIZE(m41t80_chip_info_tbl); i++) { | ||
509 | if (!strcmp(m41t80_chip_info_tbl[i].name, client->name)) { | ||
510 | chip = &m41t80_chip_info_tbl[i]; | ||
511 | break; | ||
512 | } | ||
513 | } | ||
514 | if (!chip) { | ||
515 | dev_err(&client->dev, "%s is not supported\n", client->name); | ||
516 | rc = -ENODEV; | ||
517 | goto exit; | ||
518 | } | ||
519 | |||
520 | clientdata = kzalloc(sizeof(*clientdata), GFP_KERNEL); | ||
521 | if (!clientdata) { | ||
522 | rc = -ENOMEM; | ||
523 | goto exit; | ||
524 | } | ||
525 | |||
526 | rtc = rtc_device_register(client->name, &client->dev, | ||
527 | &m41t80_rtc_ops, THIS_MODULE); | ||
528 | if (IS_ERR(rtc)) { | ||
529 | rc = PTR_ERR(rtc); | ||
530 | rtc = NULL; | ||
531 | goto exit; | ||
532 | } | ||
533 | |||
534 | clientdata->rtc = rtc; | ||
535 | clientdata->chip = chip; | ||
536 | i2c_set_clientdata(client, clientdata); | ||
537 | |||
538 | /* Make sure HT (Halt Update) bit is cleared */ | ||
539 | rc = i2c_smbus_read_byte_data(client, M41T80_REG_ALARM_HOUR); | ||
540 | if (rc < 0) | ||
541 | goto ht_err; | ||
542 | |||
543 | if (rc & M41T80_ALHOUR_HT) { | ||
544 | if (chip->features & M41T80_FEATURE_HT) { | ||
545 | m41t80_get_datetime(client, &tm); | ||
546 | dev_info(&client->dev, "HT bit was set!\n"); | ||
547 | dev_info(&client->dev, | ||
548 | "Power Down at " | ||
549 | "%04i-%02i-%02i %02i:%02i:%02i\n", | ||
550 | tm.tm_year + 1900, | ||
551 | tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, | ||
552 | tm.tm_min, tm.tm_sec); | ||
553 | } | ||
554 | if (i2c_smbus_write_byte_data(client, | ||
555 | M41T80_REG_ALARM_HOUR, | ||
556 | rc & ~M41T80_ALHOUR_HT) < 0) | ||
557 | goto ht_err; | ||
558 | } | ||
559 | |||
560 | /* Make sure ST (stop) bit is cleared */ | ||
561 | rc = i2c_smbus_read_byte_data(client, M41T80_REG_SEC); | ||
562 | if (rc < 0) | ||
563 | goto st_err; | ||
564 | |||
565 | if (rc & M41T80_SEC_ST) { | ||
566 | if (i2c_smbus_write_byte_data(client, M41T80_REG_SEC, | ||
567 | rc & ~M41T80_SEC_ST) < 0) | ||
568 | goto st_err; | ||
569 | } | ||
570 | |||
571 | rc = m41t80_sysfs_register(&client->dev); | ||
572 | if (rc) | ||
573 | goto exit; | ||
574 | |||
575 | return 0; | ||
576 | |||
577 | st_err: | ||
578 | rc = -EIO; | ||
579 | dev_err(&client->dev, "Can't clear ST bit\n"); | ||
580 | goto exit; | ||
581 | ht_err: | ||
582 | rc = -EIO; | ||
583 | dev_err(&client->dev, "Can't clear HT bit\n"); | ||
584 | goto exit; | ||
585 | |||
586 | exit: | ||
587 | if (rtc) | ||
588 | rtc_device_unregister(rtc); | ||
589 | kfree(clientdata); | ||
590 | return rc; | ||
591 | } | ||
592 | |||
593 | static int m41t80_remove(struct i2c_client *client) | ||
594 | { | ||
595 | struct m41t80_data *clientdata = i2c_get_clientdata(client); | ||
596 | struct rtc_device *rtc = clientdata->rtc; | ||
597 | |||
598 | if (rtc) | ||
599 | rtc_device_unregister(rtc); | ||
600 | kfree(clientdata); | ||
601 | |||
602 | return 0; | ||
603 | } | ||
604 | |||
605 | static struct i2c_driver m41t80_driver = { | ||
606 | .driver = { | ||
607 | .name = "m41t80", | ||
608 | }, | ||
609 | .probe = m41t80_probe, | ||
610 | .remove = m41t80_remove, | ||
611 | }; | ||
612 | |||
613 | static int __init m41t80_rtc_init(void) | ||
614 | { | ||
615 | return i2c_add_driver(&m41t80_driver); | ||
616 | } | ||
617 | |||
618 | static void __exit m41t80_rtc_exit(void) | ||
619 | { | ||
620 | i2c_del_driver(&m41t80_driver); | ||
621 | } | ||
622 | |||
623 | MODULE_AUTHOR("Alexander Bigga <ab@mycable.de>"); | ||
624 | MODULE_DESCRIPTION("ST Microelectronics M41T80 series RTC I2C Client Driver"); | ||
625 | MODULE_LICENSE("GPL"); | ||
626 | MODULE_VERSION(DRV_VERSION); | ||
627 | |||
628 | module_init(m41t80_rtc_init); | ||
629 | module_exit(m41t80_rtc_exit); | ||