diff options
author | G. Liakhovetski <gl@dsa-ac.de> | 2006-06-25 08:48:18 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2006-06-25 13:01:13 -0400 |
commit | 9c0c570576d02000063e28faadcce8c07396755d (patch) | |
tree | a8ffc618199ea4f91df839642cc7223868364ff0 /drivers/rtc | |
parent | 655066c3835e7b51794c4d56f042eb78b5a79f53 (diff) |
[PATCH] drivers/acorn/char/pcf8583.[hc] vs. RTC subsystem
A port of the driver for the pcf8583 i2c rtc controller to the generic RTC
framework by Alessandro Zummo. Based on
drivers/acorn/char/{pcf8583.[hc],i2c.c}. Hopefully, acorn can be converted
too to use this driver in the future.
Signed-off-by: G. Liakhovetski <gl@dsa-ac.de>
Signed-off-by: Alessandro Zummo <a.zummo@towertech.it>
Cc: Russell King <rmk@arm.linux.org.uk>
Cc: Jean Delvare <khali@linux-fr.org>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'drivers/rtc')
-rw-r--r-- | drivers/rtc/Kconfig | 10 | ||||
-rw-r--r-- | drivers/rtc/Makefile | 1 | ||||
-rw-r--r-- | drivers/rtc/rtc-pcf8583.c | 394 |
3 files changed, 405 insertions, 0 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 725d6b696792..37ee69f57a0b 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig | |||
@@ -132,6 +132,16 @@ config RTC_DRV_PCF8563 | |||
132 | This driver can also be built as a module. If so, the module | 132 | This driver can also be built as a module. If so, the module |
133 | will be called rtc-pcf8563. | 133 | will be called rtc-pcf8563. |
134 | 134 | ||
135 | config RTC_DRV_PCF8583 | ||
136 | tristate "Philips PCF8583" | ||
137 | depends on RTC_CLASS && I2C | ||
138 | help | ||
139 | If you say yes here you get support for the | ||
140 | Philips PCF8583 RTC chip. | ||
141 | |||
142 | This driver can also be built as a module. If so, the module | ||
143 | will be called rtc-pcf8583. | ||
144 | |||
135 | config RTC_DRV_RS5C372 | 145 | config RTC_DRV_RS5C372 |
136 | tristate "Ricoh RS5C372A/B" | 146 | tristate "Ricoh RS5C372A/B" |
137 | depends on RTC_CLASS && I2C | 147 | depends on RTC_CLASS && I2C |
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index ca8bed53079f..090f197f07e8 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile | |||
@@ -16,6 +16,7 @@ obj-$(CONFIG_RTC_DRV_TEST) += rtc-test.o | |||
16 | obj-$(CONFIG_RTC_DRV_DS1307) += rtc-ds1307.o | 16 | obj-$(CONFIG_RTC_DRV_DS1307) += rtc-ds1307.o |
17 | obj-$(CONFIG_RTC_DRV_DS1672) += rtc-ds1672.o | 17 | obj-$(CONFIG_RTC_DRV_DS1672) += rtc-ds1672.o |
18 | obj-$(CONFIG_RTC_DRV_PCF8563) += rtc-pcf8563.o | 18 | obj-$(CONFIG_RTC_DRV_PCF8563) += rtc-pcf8563.o |
19 | obj-$(CONFIG_RTC_DRV_PCF8583) += rtc-pcf8583.o | ||
19 | obj-$(CONFIG_RTC_DRV_RS5C372) += rtc-rs5c372.o | 20 | obj-$(CONFIG_RTC_DRV_RS5C372) += rtc-rs5c372.o |
20 | obj-$(CONFIG_RTC_DRV_M48T86) += rtc-m48t86.o | 21 | obj-$(CONFIG_RTC_DRV_M48T86) += rtc-m48t86.o |
21 | obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o | 22 | obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o |
diff --git a/drivers/rtc/rtc-pcf8583.c b/drivers/rtc/rtc-pcf8583.c new file mode 100644 index 000000000000..b235a30cb661 --- /dev/null +++ b/drivers/rtc/rtc-pcf8583.c | |||
@@ -0,0 +1,394 @@ | |||
1 | /* | ||
2 | * drivers/rtc/rtc-pcf8583.c | ||
3 | * | ||
4 | * Copyright (C) 2000 Russell King | ||
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 version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | * | ||
10 | * Driver for PCF8583 RTC & RAM chip | ||
11 | * | ||
12 | * Converted to the generic RTC susbsystem by G. Liakhovetski (2006) | ||
13 | */ | ||
14 | #include <linux/module.h> | ||
15 | #include <linux/i2c.h> | ||
16 | #include <linux/slab.h> | ||
17 | #include <linux/string.h> | ||
18 | #include <linux/mc146818rtc.h> | ||
19 | #include <linux/init.h> | ||
20 | #include <linux/errno.h> | ||
21 | #include <linux/bcd.h> | ||
22 | |||
23 | struct rtc_mem { | ||
24 | unsigned int loc; | ||
25 | unsigned int nr; | ||
26 | unsigned char *data; | ||
27 | }; | ||
28 | |||
29 | struct pcf8583 { | ||
30 | struct i2c_client client; | ||
31 | struct rtc_device *rtc; | ||
32 | unsigned char ctrl; | ||
33 | }; | ||
34 | |||
35 | #define CTRL_STOP 0x80 | ||
36 | #define CTRL_HOLD 0x40 | ||
37 | #define CTRL_32KHZ 0x00 | ||
38 | #define CTRL_MASK 0x08 | ||
39 | #define CTRL_ALARMEN 0x04 | ||
40 | #define CTRL_ALARM 0x02 | ||
41 | #define CTRL_TIMER 0x01 | ||
42 | |||
43 | static unsigned short normal_i2c[] = { I2C_CLIENT_END }; | ||
44 | |||
45 | /* Module parameters */ | ||
46 | I2C_CLIENT_INSMOD; | ||
47 | |||
48 | static struct i2c_driver pcf8583_driver; | ||
49 | |||
50 | #define get_ctrl(x) ((struct pcf8583 *)i2c_get_clientdata(x))->ctrl | ||
51 | #define set_ctrl(x, v) get_ctrl(x) = v | ||
52 | |||
53 | #define CMOS_YEAR (64 + 128) | ||
54 | #define CMOS_CHECKSUM (63) | ||
55 | |||
56 | static int pcf8583_get_datetime(struct i2c_client *client, struct rtc_time *dt) | ||
57 | { | ||
58 | unsigned char buf[8], addr[1] = { 1 }; | ||
59 | struct i2c_msg msgs[2] = { | ||
60 | { | ||
61 | .addr = client->addr, | ||
62 | .flags = 0, | ||
63 | .len = 1, | ||
64 | .buf = addr, | ||
65 | }, { | ||
66 | .addr = client->addr, | ||
67 | .flags = I2C_M_RD, | ||
68 | .len = 6, | ||
69 | .buf = buf, | ||
70 | } | ||
71 | }; | ||
72 | int ret; | ||
73 | |||
74 | memset(buf, 0, sizeof(buf)); | ||
75 | |||
76 | ret = i2c_transfer(client->adapter, msgs, 2); | ||
77 | if (ret == 2) { | ||
78 | dt->tm_year = buf[4] >> 6; | ||
79 | dt->tm_wday = buf[5] >> 5; | ||
80 | |||
81 | buf[4] &= 0x3f; | ||
82 | buf[5] &= 0x1f; | ||
83 | |||
84 | dt->tm_sec = BCD_TO_BIN(buf[1]); | ||
85 | dt->tm_min = BCD_TO_BIN(buf[2]); | ||
86 | dt->tm_hour = BCD_TO_BIN(buf[3]); | ||
87 | dt->tm_mday = BCD_TO_BIN(buf[4]); | ||
88 | dt->tm_mon = BCD_TO_BIN(buf[5]); | ||
89 | } | ||
90 | |||
91 | return ret == 2 ? 0 : -EIO; | ||
92 | } | ||
93 | |||
94 | static int pcf8583_set_datetime(struct i2c_client *client, struct rtc_time *dt, int datetoo) | ||
95 | { | ||
96 | unsigned char buf[8]; | ||
97 | int ret, len = 6; | ||
98 | |||
99 | buf[0] = 0; | ||
100 | buf[1] = get_ctrl(client) | 0x80; | ||
101 | buf[2] = 0; | ||
102 | buf[3] = BIN_TO_BCD(dt->tm_sec); | ||
103 | buf[4] = BIN_TO_BCD(dt->tm_min); | ||
104 | buf[5] = BIN_TO_BCD(dt->tm_hour); | ||
105 | |||
106 | if (datetoo) { | ||
107 | len = 8; | ||
108 | buf[6] = BIN_TO_BCD(dt->tm_mday) | (dt->tm_year << 6); | ||
109 | buf[7] = BIN_TO_BCD(dt->tm_mon) | (dt->tm_wday << 5); | ||
110 | } | ||
111 | |||
112 | ret = i2c_master_send(client, (char *)buf, len); | ||
113 | if (ret != len) | ||
114 | return -EIO; | ||
115 | |||
116 | buf[1] = get_ctrl(client); | ||
117 | ret = i2c_master_send(client, (char *)buf, 2); | ||
118 | |||
119 | return ret == 2 ? 0 : -EIO; | ||
120 | } | ||
121 | |||
122 | static int pcf8583_get_ctrl(struct i2c_client *client, unsigned char *ctrl) | ||
123 | { | ||
124 | *ctrl = get_ctrl(client); | ||
125 | return 0; | ||
126 | } | ||
127 | |||
128 | static int pcf8583_set_ctrl(struct i2c_client *client, unsigned char *ctrl) | ||
129 | { | ||
130 | unsigned char buf[2]; | ||
131 | |||
132 | buf[0] = 0; | ||
133 | buf[1] = *ctrl; | ||
134 | set_ctrl(client, *ctrl); | ||
135 | |||
136 | return i2c_master_send(client, (char *)buf, 2); | ||
137 | } | ||
138 | |||
139 | static int pcf8583_read_mem(struct i2c_client *client, struct rtc_mem *mem) | ||
140 | { | ||
141 | unsigned char addr[1]; | ||
142 | struct i2c_msg msgs[2] = { | ||
143 | { | ||
144 | .addr = client->addr, | ||
145 | .flags = 0, | ||
146 | .len = 1, | ||
147 | .buf = addr, | ||
148 | }, { | ||
149 | .addr = client->addr, | ||
150 | .flags = I2C_M_RD, | ||
151 | .len = mem->nr, | ||
152 | .buf = mem->data, | ||
153 | } | ||
154 | }; | ||
155 | |||
156 | if (mem->loc < 8) | ||
157 | return -EINVAL; | ||
158 | |||
159 | addr[0] = mem->loc; | ||
160 | |||
161 | return i2c_transfer(client->adapter, msgs, 2) == 2 ? 0 : -EIO; | ||
162 | } | ||
163 | |||
164 | static int pcf8583_write_mem(struct i2c_client *client, struct rtc_mem *mem) | ||
165 | { | ||
166 | unsigned char addr[1]; | ||
167 | struct i2c_msg msgs[2] = { | ||
168 | { | ||
169 | .addr = client->addr, | ||
170 | .flags = 0, | ||
171 | .len = 1, | ||
172 | .buf = addr, | ||
173 | }, { | ||
174 | .addr = client->addr, | ||
175 | .flags = I2C_M_NOSTART, | ||
176 | .len = mem->nr, | ||
177 | .buf = mem->data, | ||
178 | } | ||
179 | }; | ||
180 | |||
181 | if (mem->loc < 8) | ||
182 | return -EINVAL; | ||
183 | |||
184 | addr[0] = mem->loc; | ||
185 | |||
186 | return i2c_transfer(client->adapter, msgs, 2) == 2 ? 0 : -EIO; | ||
187 | } | ||
188 | |||
189 | static int pcf8583_rtc_read_time(struct device *dev, struct rtc_time *tm) | ||
190 | { | ||
191 | struct i2c_client *client = to_i2c_client(dev); | ||
192 | unsigned char ctrl, year[2]; | ||
193 | struct rtc_mem mem = { CMOS_YEAR, sizeof(year), year }; | ||
194 | int real_year, year_offset, err; | ||
195 | |||
196 | /* | ||
197 | * Ensure that the RTC is running. | ||
198 | */ | ||
199 | pcf8583_get_ctrl(client, &ctrl); | ||
200 | if (ctrl & (CTRL_STOP | CTRL_HOLD)) { | ||
201 | unsigned char new_ctrl = ctrl & ~(CTRL_STOP | CTRL_HOLD); | ||
202 | |||
203 | printk(KERN_WARNING "RTC: resetting control %02x -> %02x\n", | ||
204 | ctrl, new_ctrl); | ||
205 | |||
206 | if ((err = pcf8583_set_ctrl(client, &new_ctrl)) < 0) | ||
207 | return err; | ||
208 | } | ||
209 | |||
210 | if (pcf8583_get_datetime(client, tm) || | ||
211 | pcf8583_read_mem(client, &mem)) | ||
212 | return -EIO; | ||
213 | |||
214 | real_year = year[0]; | ||
215 | |||
216 | /* | ||
217 | * The RTC year holds the LSB two bits of the current | ||
218 | * year, which should reflect the LSB two bits of the | ||
219 | * CMOS copy of the year. Any difference indicates | ||
220 | * that we have to correct the CMOS version. | ||
221 | */ | ||
222 | year_offset = tm->tm_year - (real_year & 3); | ||
223 | if (year_offset < 0) | ||
224 | /* | ||
225 | * RTC year wrapped. Adjust it appropriately. | ||
226 | */ | ||
227 | year_offset += 4; | ||
228 | |||
229 | tm->tm_year = real_year + year_offset + year[1] * 100; | ||
230 | |||
231 | return 0; | ||
232 | } | ||
233 | |||
234 | static int pcf8583_rtc_set_time(struct device *dev, struct rtc_time *tm) | ||
235 | { | ||
236 | struct i2c_client *client = to_i2c_client(dev); | ||
237 | unsigned char year[2], chk; | ||
238 | struct rtc_mem cmos_year = { CMOS_YEAR, sizeof(year), year }; | ||
239 | struct rtc_mem cmos_check = { CMOS_CHECKSUM, 1, &chk }; | ||
240 | int ret; | ||
241 | |||
242 | /* | ||
243 | * The RTC's own 2-bit year must reflect the least | ||
244 | * significant two bits of the CMOS year. | ||
245 | */ | ||
246 | |||
247 | ret = pcf8583_set_datetime(client, tm, 1); | ||
248 | if (ret) | ||
249 | return ret; | ||
250 | |||
251 | ret = pcf8583_read_mem(client, &cmos_check); | ||
252 | if (ret) | ||
253 | return ret; | ||
254 | |||
255 | ret = pcf8583_read_mem(client, &cmos_year); | ||
256 | if (ret) | ||
257 | return ret; | ||
258 | |||
259 | chk -= year[1] + year[0]; | ||
260 | |||
261 | year[1] = tm->tm_year / 100; | ||
262 | year[0] = tm->tm_year % 100; | ||
263 | |||
264 | chk += year[1] + year[0]; | ||
265 | |||
266 | ret = pcf8583_write_mem(client, &cmos_year); | ||
267 | |||
268 | if (ret) | ||
269 | return ret; | ||
270 | |||
271 | ret = pcf8583_write_mem(client, &cmos_check); | ||
272 | |||
273 | return ret; | ||
274 | } | ||
275 | |||
276 | static struct rtc_class_ops pcf8583_rtc_ops = { | ||
277 | .read_time = pcf8583_rtc_read_time, | ||
278 | .set_time = pcf8583_rtc_set_time, | ||
279 | }; | ||
280 | |||
281 | static int pcf8583_probe(struct i2c_adapter *adap, int addr, int kind); | ||
282 | |||
283 | static int pcf8583_attach(struct i2c_adapter *adap) | ||
284 | { | ||
285 | return i2c_probe(adap, &addr_data, pcf8583_probe); | ||
286 | } | ||
287 | |||
288 | static int pcf8583_detach(struct i2c_client *client) | ||
289 | { | ||
290 | int err; | ||
291 | struct pcf8583 *pcf = i2c_get_clientdata(client); | ||
292 | struct rtc_device *rtc = pcf->rtc; | ||
293 | |||
294 | if (rtc) | ||
295 | rtc_device_unregister(rtc); | ||
296 | |||
297 | if ((err = i2c_detach_client(client))) | ||
298 | return err; | ||
299 | |||
300 | kfree(pcf); | ||
301 | return 0; | ||
302 | } | ||
303 | |||
304 | static struct i2c_driver pcf8583_driver = { | ||
305 | .driver = { | ||
306 | .name = "pcf8583", | ||
307 | }, | ||
308 | .id = I2C_DRIVERID_PCF8583, | ||
309 | .attach_adapter = pcf8583_attach, | ||
310 | .detach_client = pcf8583_detach, | ||
311 | }; | ||
312 | |||
313 | static int pcf8583_probe(struct i2c_adapter *adap, int addr, int kind) | ||
314 | { | ||
315 | struct pcf8583 *pcf; | ||
316 | struct i2c_client *client; | ||
317 | struct rtc_device *rtc; | ||
318 | unsigned char buf[1], ad[1] = { 0 }; | ||
319 | int err; | ||
320 | struct i2c_msg msgs[2] = { | ||
321 | { | ||
322 | .addr = addr, | ||
323 | .flags = 0, | ||
324 | .len = 1, | ||
325 | .buf = ad, | ||
326 | }, { | ||
327 | .addr = addr, | ||
328 | .flags = I2C_M_RD, | ||
329 | .len = 1, | ||
330 | .buf = buf, | ||
331 | } | ||
332 | }; | ||
333 | |||
334 | pcf = kzalloc(sizeof(*pcf), GFP_KERNEL); | ||
335 | if (!pcf) | ||
336 | return -ENOMEM; | ||
337 | |||
338 | client = &pcf->client; | ||
339 | |||
340 | client->addr = addr; | ||
341 | client->adapter = adap; | ||
342 | client->driver = &pcf8583_driver; | ||
343 | |||
344 | strlcpy(client->name, pcf8583_driver.driver.name, I2C_NAME_SIZE); | ||
345 | |||
346 | if (i2c_transfer(client->adapter, msgs, 2) != 2) { | ||
347 | err = -EIO; | ||
348 | goto exit_kfree; | ||
349 | } | ||
350 | |||
351 | err = i2c_attach_client(client); | ||
352 | |||
353 | if (err) | ||
354 | goto exit_kfree; | ||
355 | |||
356 | rtc = rtc_device_register(pcf8583_driver.driver.name, &client->dev, | ||
357 | &pcf8583_rtc_ops, THIS_MODULE); | ||
358 | |||
359 | if (IS_ERR(rtc)) { | ||
360 | err = PTR_ERR(rtc); | ||
361 | goto exit_detach; | ||
362 | } | ||
363 | |||
364 | pcf->rtc = rtc; | ||
365 | i2c_set_clientdata(client, pcf); | ||
366 | set_ctrl(client, buf[0]); | ||
367 | |||
368 | return 0; | ||
369 | |||
370 | exit_detach: | ||
371 | i2c_detach_client(client); | ||
372 | |||
373 | exit_kfree: | ||
374 | kfree(pcf); | ||
375 | |||
376 | return err; | ||
377 | } | ||
378 | |||
379 | static __init int pcf8583_init(void) | ||
380 | { | ||
381 | return i2c_add_driver(&pcf8583_driver); | ||
382 | } | ||
383 | |||
384 | static __exit void pcf8583_exit(void) | ||
385 | { | ||
386 | i2c_del_driver(&pcf8583_driver); | ||
387 | } | ||
388 | |||
389 | module_init(pcf8583_init); | ||
390 | module_exit(pcf8583_exit); | ||
391 | |||
392 | MODULE_AUTHOR("Russell King"); | ||
393 | MODULE_DESCRIPTION("PCF8583 I2C RTC driver"); | ||
394 | MODULE_LICENSE("GPL"); | ||