Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * linux/drivers/i2c/chips/rtc8564.c | |
3 | * | |
4 | * Copyright (C) 2002-2004 Stefan Eletzhofer | |
5 | * | |
6 | * based on linux/drivers/acron/char/pcf8583.c | |
7 | * Copyright (C) 2000 Russell King | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License version 2 as | |
11 | * published by the Free Software Foundation. | |
12 | * | |
13 | * Driver for system3's EPSON RTC 8564 chip | |
14 | */ | |
15 | #include <linux/module.h> | |
16 | #include <linux/kernel.h> | |
7e944369 | 17 | #include <linux/bcd.h> |
1da177e4 LT |
18 | #include <linux/i2c.h> |
19 | #include <linux/slab.h> | |
20 | #include <linux/string.h> | |
21 | #include <linux/rtc.h> /* get the user-level API */ | |
22 | #include <linux/init.h> | |
1da177e4 LT |
23 | |
24 | #include "rtc8564.h" | |
25 | ||
26 | #ifdef DEBUG | |
27 | # define _DBG(x, fmt, args...) do{ if (debug>=x) printk(KERN_DEBUG"%s: " fmt "\n", __FUNCTION__, ##args); } while(0); | |
28 | #else | |
29 | # define _DBG(x, fmt, args...) do { } while(0); | |
30 | #endif | |
31 | ||
32 | #define _DBGRTCTM(x, rtctm) if (debug>=x) printk("%s: secs=%d, mins=%d, hours=%d, mday=%d, " \ | |
33 | "mon=%d, year=%d, wday=%d VL=%d\n", __FUNCTION__, \ | |
34 | (rtctm).secs, (rtctm).mins, (rtctm).hours, (rtctm).mday, \ | |
35 | (rtctm).mon, (rtctm).year, (rtctm).wday, (rtctm).vl); | |
36 | ||
37 | struct rtc8564_data { | |
38 | struct i2c_client client; | |
39 | u16 ctrl; | |
40 | }; | |
41 | ||
42 | static inline u8 _rtc8564_ctrl1(struct i2c_client *client) | |
43 | { | |
44 | struct rtc8564_data *data = i2c_get_clientdata(client); | |
45 | return data->ctrl & 0xff; | |
46 | } | |
47 | static inline u8 _rtc8564_ctrl2(struct i2c_client *client) | |
48 | { | |
49 | struct rtc8564_data *data = i2c_get_clientdata(client); | |
50 | return (data->ctrl & 0xff00) >> 8; | |
51 | } | |
52 | ||
53 | #define CTRL1(c) _rtc8564_ctrl1(c) | |
54 | #define CTRL2(c) _rtc8564_ctrl2(c) | |
55 | ||
1da177e4 LT |
56 | static int debug;; |
57 | module_param(debug, int, S_IRUGO | S_IWUSR); | |
58 | ||
59 | static struct i2c_driver rtc8564_driver; | |
60 | ||
61 | static unsigned short ignore[] = { I2C_CLIENT_END }; | |
62 | static unsigned short normal_addr[] = { 0x51, I2C_CLIENT_END }; | |
63 | ||
64 | static struct i2c_client_address_data addr_data = { | |
65 | .normal_i2c = normal_addr, | |
1da177e4 | 66 | .probe = ignore, |
1da177e4 | 67 | .ignore = ignore, |
1da177e4 LT |
68 | }; |
69 | ||
70 | static int rtc8564_read_mem(struct i2c_client *client, struct mem *mem); | |
71 | static int rtc8564_write_mem(struct i2c_client *client, struct mem *mem); | |
72 | ||
73 | static int rtc8564_read(struct i2c_client *client, unsigned char adr, | |
74 | unsigned char *buf, unsigned char len) | |
75 | { | |
76 | int ret = -EIO; | |
77 | unsigned char addr[1] = { adr }; | |
78 | struct i2c_msg msgs[2] = { | |
79 | {client->addr, 0, 1, addr}, | |
80 | {client->addr, I2C_M_RD, len, buf} | |
81 | }; | |
82 | ||
83 | _DBG(1, "client=%p, adr=%d, buf=%p, len=%d", client, adr, buf, len); | |
84 | ||
85 | if (!buf) { | |
86 | ret = -EINVAL; | |
87 | goto done; | |
88 | } | |
89 | ||
90 | ret = i2c_transfer(client->adapter, msgs, 2); | |
91 | if (ret == 2) { | |
92 | ret = 0; | |
93 | } | |
94 | ||
95 | done: | |
96 | return ret; | |
97 | } | |
98 | ||
99 | static int rtc8564_write(struct i2c_client *client, unsigned char adr, | |
100 | unsigned char *data, unsigned char len) | |
101 | { | |
102 | int ret = 0; | |
103 | unsigned char _data[16]; | |
104 | struct i2c_msg wr; | |
105 | int i; | |
106 | ||
107 | if (!data || len > 15) { | |
108 | ret = -EINVAL; | |
109 | goto done; | |
110 | } | |
111 | ||
112 | _DBG(1, "client=%p, adr=%d, buf=%p, len=%d", client, adr, data, len); | |
113 | ||
114 | _data[0] = adr; | |
115 | for (i = 0; i < len; i++) { | |
116 | _data[i + 1] = data[i]; | |
117 | _DBG(5, "data[%d] = 0x%02x (%d)", i, data[i], data[i]); | |
118 | } | |
119 | ||
120 | wr.addr = client->addr; | |
121 | wr.flags = 0; | |
122 | wr.len = len + 1; | |
123 | wr.buf = _data; | |
124 | ||
125 | ret = i2c_transfer(client->adapter, &wr, 1); | |
126 | if (ret == 1) { | |
127 | ret = 0; | |
128 | } | |
129 | ||
130 | done: | |
131 | return ret; | |
132 | } | |
133 | ||
134 | static int rtc8564_attach(struct i2c_adapter *adap, int addr, int kind) | |
135 | { | |
136 | int ret; | |
137 | struct i2c_client *new_client; | |
138 | struct rtc8564_data *d; | |
139 | unsigned char data[10]; | |
140 | unsigned char ad[1] = { 0 }; | |
141 | struct i2c_msg ctrl_wr[1] = { | |
142 | {addr, 0, 2, data} | |
143 | }; | |
144 | struct i2c_msg ctrl_rd[2] = { | |
145 | {addr, 0, 1, ad}, | |
146 | {addr, I2C_M_RD, 2, data} | |
147 | }; | |
148 | ||
5263ebb5 | 149 | d = kzalloc(sizeof(struct rtc8564_data), GFP_KERNEL); |
1da177e4 LT |
150 | if (!d) { |
151 | ret = -ENOMEM; | |
152 | goto done; | |
153 | } | |
1da177e4 LT |
154 | new_client = &d->client; |
155 | ||
156 | strlcpy(new_client->name, "RTC8564", I2C_NAME_SIZE); | |
157 | i2c_set_clientdata(new_client, d); | |
00bffb6e | 158 | new_client->flags = I2C_CLIENT_ALLOW_USE; |
1da177e4 LT |
159 | new_client->addr = addr; |
160 | new_client->adapter = adap; | |
161 | new_client->driver = &rtc8564_driver; | |
162 | ||
163 | _DBG(1, "client=%p", new_client); | |
164 | ||
165 | /* init ctrl1 reg */ | |
166 | data[0] = 0; | |
167 | data[1] = 0; | |
168 | ret = i2c_transfer(new_client->adapter, ctrl_wr, 1); | |
169 | if (ret != 1) { | |
170 | printk(KERN_INFO "rtc8564: cant init ctrl1\n"); | |
171 | ret = -ENODEV; | |
172 | goto done; | |
173 | } | |
174 | ||
175 | /* read back ctrl1 and ctrl2 */ | |
176 | ret = i2c_transfer(new_client->adapter, ctrl_rd, 2); | |
177 | if (ret != 2) { | |
178 | printk(KERN_INFO "rtc8564: cant read ctrl\n"); | |
179 | ret = -ENODEV; | |
180 | goto done; | |
181 | } | |
182 | ||
183 | d->ctrl = data[0] | (data[1] << 8); | |
184 | ||
185 | _DBG(1, "RTC8564_REG_CTRL1=%02x, RTC8564_REG_CTRL2=%02x", | |
186 | data[0], data[1]); | |
187 | ||
188 | ret = i2c_attach_client(new_client); | |
189 | done: | |
190 | if (ret) { | |
191 | kfree(d); | |
192 | } | |
193 | return ret; | |
194 | } | |
195 | ||
196 | static int rtc8564_probe(struct i2c_adapter *adap) | |
197 | { | |
198 | return i2c_probe(adap, &addr_data, rtc8564_attach); | |
199 | } | |
200 | ||
201 | static int rtc8564_detach(struct i2c_client *client) | |
202 | { | |
203 | i2c_detach_client(client); | |
204 | kfree(i2c_get_clientdata(client)); | |
205 | return 0; | |
206 | } | |
207 | ||
208 | static int rtc8564_get_datetime(struct i2c_client *client, struct rtc_tm *dt) | |
209 | { | |
210 | int ret = -EIO; | |
211 | unsigned char buf[15]; | |
212 | ||
213 | _DBG(1, "client=%p, dt=%p", client, dt); | |
214 | ||
215 | if (!dt) | |
216 | return -EINVAL; | |
217 | ||
218 | memset(buf, 0, sizeof(buf)); | |
219 | ||
220 | ret = rtc8564_read(client, 0, buf, 15); | |
221 | if (ret) | |
222 | return ret; | |
223 | ||
224 | /* century stored in minute alarm reg */ | |
7e944369 NK |
225 | dt->year = BCD2BIN(buf[RTC8564_REG_YEAR]); |
226 | dt->year += 100 * BCD2BIN(buf[RTC8564_REG_AL_MIN] & 0x3f); | |
227 | dt->mday = BCD2BIN(buf[RTC8564_REG_DAY] & 0x3f); | |
228 | dt->wday = BCD2BIN(buf[RTC8564_REG_WDAY] & 7); | |
229 | dt->mon = BCD2BIN(buf[RTC8564_REG_MON_CENT] & 0x1f); | |
1da177e4 | 230 | |
7e944369 | 231 | dt->secs = BCD2BIN(buf[RTC8564_REG_SEC] & 0x7f); |
1da177e4 | 232 | dt->vl = (buf[RTC8564_REG_SEC] & 0x80) == 0x80; |
7e944369 NK |
233 | dt->mins = BCD2BIN(buf[RTC8564_REG_MIN] & 0x7f); |
234 | dt->hours = BCD2BIN(buf[RTC8564_REG_HR] & 0x3f); | |
1da177e4 LT |
235 | |
236 | _DBGRTCTM(2, *dt); | |
237 | ||
238 | return 0; | |
239 | } | |
240 | ||
241 | static int | |
242 | rtc8564_set_datetime(struct i2c_client *client, struct rtc_tm *dt, int datetoo) | |
243 | { | |
244 | int ret, len = 5; | |
245 | unsigned char buf[15]; | |
246 | ||
247 | _DBG(1, "client=%p, dt=%p", client, dt); | |
248 | ||
249 | if (!dt) | |
250 | return -EINVAL; | |
251 | ||
252 | _DBGRTCTM(2, *dt); | |
253 | ||
254 | buf[RTC8564_REG_CTRL1] = CTRL1(client) | RTC8564_CTRL1_STOP; | |
255 | buf[RTC8564_REG_CTRL2] = CTRL2(client); | |
7e944369 NK |
256 | buf[RTC8564_REG_SEC] = BIN2BCD(dt->secs); |
257 | buf[RTC8564_REG_MIN] = BIN2BCD(dt->mins); | |
258 | buf[RTC8564_REG_HR] = BIN2BCD(dt->hours); | |
1da177e4 LT |
259 | |
260 | if (datetoo) { | |
261 | len += 5; | |
7e944369 NK |
262 | buf[RTC8564_REG_DAY] = BIN2BCD(dt->mday); |
263 | buf[RTC8564_REG_WDAY] = BIN2BCD(dt->wday); | |
264 | buf[RTC8564_REG_MON_CENT] = BIN2BCD(dt->mon) & 0x1f; | |
1da177e4 | 265 | /* century stored in minute alarm reg */ |
7e944369 NK |
266 | buf[RTC8564_REG_YEAR] = BIN2BCD(dt->year % 100); |
267 | buf[RTC8564_REG_AL_MIN] = BIN2BCD(dt->year / 100); | |
1da177e4 LT |
268 | } |
269 | ||
270 | ret = rtc8564_write(client, 0, buf, len); | |
271 | if (ret) { | |
272 | _DBG(1, "error writing data! %d", ret); | |
273 | } | |
274 | ||
275 | buf[RTC8564_REG_CTRL1] = CTRL1(client); | |
276 | ret = rtc8564_write(client, 0, buf, 1); | |
277 | if (ret) { | |
278 | _DBG(1, "error writing data! %d", ret); | |
279 | } | |
280 | ||
281 | return ret; | |
282 | } | |
283 | ||
284 | static int rtc8564_get_ctrl(struct i2c_client *client, unsigned int *ctrl) | |
285 | { | |
286 | struct rtc8564_data *data = i2c_get_clientdata(client); | |
287 | ||
288 | if (!ctrl) | |
289 | return -1; | |
290 | ||
291 | *ctrl = data->ctrl; | |
292 | return 0; | |
293 | } | |
294 | ||
295 | static int rtc8564_set_ctrl(struct i2c_client *client, unsigned int *ctrl) | |
296 | { | |
297 | struct rtc8564_data *data = i2c_get_clientdata(client); | |
298 | unsigned char buf[2]; | |
299 | ||
300 | if (!ctrl) | |
301 | return -1; | |
302 | ||
303 | buf[0] = *ctrl & 0xff; | |
304 | buf[1] = (*ctrl & 0xff00) >> 8; | |
305 | data->ctrl = *ctrl; | |
306 | ||
307 | return rtc8564_write(client, 0, buf, 2); | |
308 | } | |
309 | ||
310 | static int rtc8564_read_mem(struct i2c_client *client, struct mem *mem) | |
311 | { | |
312 | ||
313 | if (!mem) | |
314 | return -EINVAL; | |
315 | ||
316 | return rtc8564_read(client, mem->loc, mem->data, mem->nr); | |
317 | } | |
318 | ||
319 | static int rtc8564_write_mem(struct i2c_client *client, struct mem *mem) | |
320 | { | |
321 | ||
322 | if (!mem) | |
323 | return -EINVAL; | |
324 | ||
325 | return rtc8564_write(client, mem->loc, mem->data, mem->nr); | |
326 | } | |
327 | ||
328 | static int | |
329 | rtc8564_command(struct i2c_client *client, unsigned int cmd, void *arg) | |
330 | { | |
331 | ||
332 | _DBG(1, "cmd=%d", cmd); | |
333 | ||
334 | switch (cmd) { | |
335 | case RTC_GETDATETIME: | |
336 | return rtc8564_get_datetime(client, arg); | |
337 | ||
338 | case RTC_SETTIME: | |
339 | return rtc8564_set_datetime(client, arg, 0); | |
340 | ||
341 | case RTC_SETDATETIME: | |
342 | return rtc8564_set_datetime(client, arg, 1); | |
343 | ||
344 | case RTC_GETCTRL: | |
345 | return rtc8564_get_ctrl(client, arg); | |
346 | ||
347 | case RTC_SETCTRL: | |
348 | return rtc8564_set_ctrl(client, arg); | |
349 | ||
350 | case MEM_READ: | |
351 | return rtc8564_read_mem(client, arg); | |
352 | ||
353 | case MEM_WRITE: | |
354 | return rtc8564_write_mem(client, arg); | |
355 | ||
356 | default: | |
357 | return -EINVAL; | |
358 | } | |
359 | } | |
360 | ||
361 | static struct i2c_driver rtc8564_driver = { | |
362 | .owner = THIS_MODULE, | |
363 | .name = "RTC8564", | |
364 | .id = I2C_DRIVERID_RTC8564, | |
1da177e4 LT |
365 | .attach_adapter = rtc8564_probe, |
366 | .detach_client = rtc8564_detach, | |
367 | .command = rtc8564_command | |
368 | }; | |
369 | ||
370 | static __init int rtc8564_init(void) | |
371 | { | |
372 | return i2c_add_driver(&rtc8564_driver); | |
373 | } | |
374 | ||
375 | static __exit void rtc8564_exit(void) | |
376 | { | |
377 | i2c_del_driver(&rtc8564_driver); | |
378 | } | |
379 | ||
380 | MODULE_AUTHOR("Stefan Eletzhofer <Stefan.Eletzhofer@eletztrick.de>"); | |
381 | MODULE_DESCRIPTION("EPSON RTC8564 Driver"); | |
382 | MODULE_LICENSE("GPL"); | |
383 | ||
384 | module_init(rtc8564_init); | |
385 | module_exit(rtc8564_exit); |