OpenWrt – Blame information for rev 4
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
4 | office | 1 | /* |
2 | * RTC client/driver for the Whwave SD2068 Real-Time Clock over I2C |
||
3 | * |
||
4 | * Copyright (C) 2013 Tang, Haifeng <tanghaifeng-gz@loongson.cn>. |
||
5 | * |
||
6 | * base on ds3232 |
||
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 SD2068_SECONDS 0x00 |
||
28 | #define SD2068_MINUTES 0x01 |
||
29 | #define SD2068_HOURS 0x02 |
||
30 | #define SD2068_AMPM 0x02 |
||
31 | #define SD2068_DAY 0x03 |
||
32 | #define SD2068_DATE 0x04 |
||
33 | #define SD2068_MONTH 0x05 |
||
34 | #define SD2068_YEAR 0x06 |
||
35 | |||
36 | #define SD2068_ALARM 0x07 /* Alarm 1 BASE */ |
||
37 | |||
38 | #define SD2068_ALARM_EN 0x0E |
||
39 | # define SD2068_ALARM_EAY 0x40 |
||
40 | # define SD2068_ALARM_EAMO 0x20 |
||
41 | # define SD2068_ALARM_EAD 0x10 |
||
42 | # define SD2068_ALARM_EAW 0x08 |
||
43 | # define SD2068_ALARM_EAH 0x04 |
||
44 | # define SD2068_ALARM_EAMN 0x02 |
||
45 | # define SD2068_ALARM_EAS 0x01 |
||
46 | |||
47 | #define SD2068_CTR1 0x0F /* Control register 1 */ |
||
48 | # define SD2068_CTR1_WRTC3 0x80 |
||
49 | # define SD2068_CTR1_INTAF 0x20 |
||
50 | # define SD2068_CTR1_INTDF 0x10 |
||
51 | # define SD2068_CTR1_WRTC2 0x04 |
||
52 | # define SD2068_CTR1_RTCF 0x01 |
||
53 | |||
54 | #define SD2068_CTR2 0x10 |
||
55 | # define SD2068_CTR2_WRTC1 0x80 |
||
56 | # define SD2068_CTR2_IM 0x40 |
||
57 | # define SD2068_CTR2_INTS1 0x20 |
||
58 | # define SD2068_CTR2_INTS0 0x10 |
||
59 | # define SD2068_CTR2_FOBAT 0x08 |
||
60 | # define SD2068_CTR2_INTDE 0x04 |
||
61 | # define SD2068_CTR2_INTAE 0x02 |
||
62 | # define SD2068_CTR2_INTFE 0x01 |
||
63 | |||
64 | #define SD2068_CTR3 0x11 |
||
65 | # define SD2068_CTR3_ARST 0x80 |
||
66 | # define SD2068_CTR3_TDS1 0x20 |
||
67 | # define SD2068_CTR3_TDS0 0x10 |
||
68 | # define SD2068_CTR3_FS3 0x08 |
||
69 | # define SD2068_CTR3_FS2 0x04 |
||
70 | # define SD2068_CTR3_FS1 0x02 |
||
71 | # define SD2068_CTR3_FS0 0x01 |
||
72 | |||
73 | #define SD2068_TIME_ADJ 0x12 |
||
74 | |||
75 | struct sd2068 { |
||
76 | struct i2c_client *client; |
||
77 | struct rtc_device *rtc; |
||
78 | struct work_struct work; |
||
79 | |||
80 | /* The mutex protects alarm operations, and prevents a race |
||
81 | * between the enable_irq() in the workqueue and the free_irq() |
||
82 | * in the remove function. |
||
83 | */ |
||
84 | struct mutex mutex; |
||
85 | int exiting; |
||
86 | }; |
||
87 | |||
88 | static void sd2068_write_enable(struct i2c_client *client) |
||
89 | { |
||
90 | char ret; |
||
91 | |||
92 | ret = i2c_smbus_read_byte_data(client, SD2068_CTR2); |
||
93 | ret = ret | SD2068_CTR2_WRTC1; |
||
94 | i2c_smbus_write_byte_data(client, SD2068_CTR2, ret); |
||
95 | |||
96 | ret = i2c_smbus_read_byte_data(client, SD2068_CTR1); |
||
97 | ret = ret | SD2068_CTR1_WRTC3 | SD2068_CTR1_WRTC2; |
||
98 | i2c_smbus_write_byte_data(client, SD2068_CTR1, ret); |
||
99 | } |
||
100 | |||
101 | static void sd2068_write_disable(struct i2c_client *client) |
||
102 | { |
||
103 | char ret; |
||
104 | |||
105 | ret = i2c_smbus_read_byte_data(client, SD2068_CTR1); |
||
106 | ret = ret & (~SD2068_CTR1_WRTC3) & (~SD2068_CTR1_WRTC2); |
||
107 | i2c_smbus_write_byte_data(client, SD2068_CTR1, ret); |
||
108 | |||
109 | ret = i2c_smbus_read_byte_data(client, SD2068_CTR2); |
||
110 | ret = ret & (~SD2068_CTR2_WRTC1); |
||
111 | i2c_smbus_write_byte_data(client, SD2068_CTR2, ret); |
||
112 | } |
||
113 | |||
114 | static void sd2068_hw_init(struct i2c_client *client) |
||
115 | { |
||
116 | char ret; |
||
117 | |||
118 | sd2068_write_enable(client); |
||
119 | |||
120 | ret = i2c_smbus_read_byte_data(client, SD2068_CTR2); |
||
121 | ret = ret & (~SD2068_CTR2_IM); /* 只使用单事件报警 */ |
||
122 | ret = ret & ((~SD2068_CTR2_INTS1) | SD2068_CTR2_INTS0); |
||
123 | ret = ret & (~SD2068_CTR2_FOBAT); |
||
124 | ret = ret & (((~SD2068_CTR2_INTDE) | SD2068_CTR2_INTAE) & (~SD2068_CTR2_INTFE)); |
||
125 | i2c_smbus_write_byte_data(client, SD2068_CTR2, ret); |
||
126 | |||
127 | ret = i2c_smbus_read_byte_data(client, SD2068_CTR3); |
||
128 | ret = ret & (~SD2068_CTR3_ARST); |
||
129 | i2c_smbus_write_byte_data(client, SD2068_CTR3, ret); |
||
130 | |||
131 | sd2068_write_disable(client); |
||
132 | } |
||
133 | |||
134 | static int sd2068_read_time(struct device *dev, struct rtc_time *time) |
||
135 | { |
||
136 | struct i2c_client *client = to_i2c_client(dev); |
||
137 | int ret; |
||
138 | u8 buf[7]; |
||
139 | unsigned char year, month, day, hour, minute, second; |
||
140 | unsigned char week, twelve_hr, am_pm; |
||
141 | |||
142 | ret = i2c_smbus_read_i2c_block_data(client, SD2068_SECONDS, 7, buf); |
||
143 | if (ret < 0) |
||
144 | return ret; |
||
145 | if (ret < 7) |
||
146 | return -EIO; |
||
147 | |||
148 | second = buf[0]; |
||
149 | minute = buf[1]; |
||
150 | hour = buf[2]; |
||
151 | week = buf[3]; |
||
152 | day = buf[4]; |
||
153 | month = buf[5]; |
||
154 | year = buf[6]; |
||
155 | |||
156 | /* Extract additional information for AM/PM */ |
||
157 | twelve_hr = hour & 0x80; |
||
158 | am_pm = hour & 0x20; |
||
159 | |||
160 | /* Write to rtc_time structure */ |
||
161 | time->tm_sec = bcd2bin(second & 0x7f); |
||
162 | time->tm_min = bcd2bin(minute & 0x7f); |
||
163 | if (twelve_hr) { |
||
164 | time->tm_hour = bcd2bin(hour & 0x3f); |
||
165 | } else { |
||
166 | /* Convert to 24 hr */ |
||
167 | if (am_pm) |
||
168 | time->tm_hour = bcd2bin(hour & 0x1f) + 12; |
||
169 | else |
||
170 | time->tm_hour = bcd2bin(hour & 0x1f); |
||
171 | } |
||
172 | |||
173 | time->tm_wday = bcd2bin(week & 0x07); |
||
174 | time->tm_mday = bcd2bin(day & 0x3f); |
||
175 | /* linux tm_mon range:0~11, while month range is 1~12 in RTC chip */ |
||
176 | time->tm_mon = bcd2bin(month & 0x7F) - 1; |
||
177 | time->tm_year = bcd2bin(year); |
||
178 | if (time->tm_year < 70) |
||
179 | time->tm_year += 100; |
||
180 | |||
181 | dev_dbg(dev, "%s secs=%d, mins=%d, " |
||
182 | "hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n", |
||
183 | "read", time->tm_sec, time->tm_min, |
||
184 | time->tm_hour, time->tm_mday, |
||
185 | time->tm_mon, time->tm_year, time->tm_wday); |
||
186 | |||
187 | return rtc_valid_tm(time); |
||
188 | } |
||
189 | |||
190 | static int sd2068_set_time(struct device *dev, struct rtc_time *time) |
||
191 | { |
||
192 | struct i2c_client *client = to_i2c_client(dev); |
||
193 | u8 buf[7]; |
||
194 | |||
195 | /* Extract time from rtc_time and load into sd2068*/ |
||
196 | sd2068_write_enable(client); |
||
197 | |||
198 | buf[0] = bin2bcd(time->tm_sec); |
||
199 | buf[1] = bin2bcd(time->tm_min); |
||
200 | buf[2] = bin2bcd(time->tm_hour) | 0x80; /* only 24 hr? */ |
||
201 | buf[3] = bin2bcd(time->tm_wday); |
||
202 | buf[4] = bin2bcd(time->tm_mday); /* Date */ |
||
203 | /* linux tm_mon range:0~11, while month range is 1~12 in RTC chip */ |
||
204 | buf[5] = bin2bcd(time->tm_mon + 1); |
||
205 | buf[6] = bin2bcd(time->tm_year % 100); |
||
206 | |||
207 | i2c_smbus_write_i2c_block_data(client, SD2068_SECONDS, 7, buf); |
||
208 | i2c_smbus_write_byte_data(client, SD2068_TIME_ADJ, 0x00); |
||
209 | |||
210 | sd2068_write_disable(client); |
||
211 | |||
212 | return 0; |
||
213 | } |
||
214 | |||
215 | /* |
||
216 | * According to linux specification, only support one-shot alarm |
||
217 | * no periodic alarm mode |
||
218 | */ |
||
219 | static int sd2068_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) |
||
220 | { |
||
221 | struct i2c_client *client = to_i2c_client(dev); |
||
222 | struct sd2068 *sd2068 = i2c_get_clientdata(client); |
||
223 | unsigned char control, alm_en; |
||
224 | unsigned char ret; |
||
225 | u8 buf[7]; |
||
226 | |||
227 | mutex_lock(&sd2068->mutex); |
||
228 | |||
229 | ret = i2c_smbus_read_byte_data(client, SD2068_CTR2); |
||
230 | if (ret < 0) |
||
231 | goto out; |
||
232 | control = ret; |
||
233 | alarm->enabled = (control & SD2068_CTR2_INTAE) ? 1 : 0; |
||
234 | |||
235 | ret = i2c_smbus_read_i2c_block_data(client, SD2068_ALARM, 7, buf); |
||
236 | if (ret < 0) |
||
237 | goto out; |
||
238 | |||
239 | ret = i2c_smbus_read_byte_data(client, SD2068_ALARM_EN); |
||
240 | if (ret < 0) |
||
241 | goto out; |
||
242 | alm_en = ret; |
||
243 | /* decode the alarm enable field */ |
||
244 | if (alm_en & SD2068_ALARM_EAS) |
||
245 | alarm->time.tm_sec = bcd2bin(buf[0] & 0x7F); |
||
246 | else |
||
247 | alarm->time.tm_sec = -1; |
||
248 | |||
249 | if (alm_en & SD2068_ALARM_EAMN) |
||
250 | alarm->time.tm_min = bcd2bin(buf[1] & 0x7F); |
||
251 | else |
||
252 | alarm->time.tm_min = -1; |
||
253 | |||
254 | if (alm_en & SD2068_ALARM_EAH) |
||
255 | alarm->time.tm_hour = bcd2bin(buf[2] & 0x3F); |
||
256 | else |
||
257 | alarm->time.tm_hour = -1; |
||
258 | |||
259 | if (alm_en & SD2068_ALARM_EAW) |
||
260 | alarm->time.tm_wday = bcd2bin(buf[3] & 0x7F); |
||
261 | else |
||
262 | alarm->time.tm_wday = -1; |
||
263 | |||
264 | if (alm_en & SD2068_ALARM_EAD) |
||
265 | alarm->time.tm_mday = bcd2bin(buf[4] & 0x3F); |
||
266 | else |
||
267 | alarm->time.tm_mday = -1; |
||
268 | |||
269 | if (alm_en & SD2068_ALARM_EAMO) |
||
270 | alarm->time.tm_mon = bcd2bin(buf[5] & 0x1F); |
||
271 | else |
||
272 | alarm->time.tm_mon = -1; |
||
273 | |||
274 | if (alm_en & SD2068_ALARM_EAY) |
||
275 | alarm->time.tm_year = bcd2bin(buf[6]); |
||
276 | else |
||
277 | alarm->time.tm_year = -1; |
||
278 | |||
279 | ret = 0; |
||
280 | out: |
||
281 | mutex_unlock(&sd2068->mutex); |
||
282 | return ret; |
||
283 | } |
||
284 | |||
285 | /* |
||
286 | * linux rtc-module does not support wday alarm |
||
287 | * and only 24h time mode supported indeed |
||
288 | */ |
||
289 | static int sd2068_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) |
||
290 | { |
||
291 | struct i2c_client *client = to_i2c_client(dev); |
||
292 | struct sd2068 *sd2068 = i2c_get_clientdata(client); |
||
293 | int control; |
||
294 | int ret; |
||
295 | u8 buf[7]; |
||
296 | |||
297 | if (client->irq <= 0) |
||
298 | return -EINVAL; |
||
299 | |||
300 | mutex_lock(&sd2068->mutex); |
||
301 | |||
302 | sd2068_write_enable(client); |
||
303 | |||
304 | buf[0] = bin2bcd(alarm->time.tm_sec); |
||
305 | buf[1] = bin2bcd(alarm->time.tm_min); |
||
306 | buf[2] = bin2bcd(alarm->time.tm_hour); |
||
307 | buf[3] = bin2bcd(alarm->time.tm_wday); |
||
308 | buf[4] = bin2bcd(alarm->time.tm_mday); |
||
309 | buf[5] = bin2bcd(alarm->time.tm_mon); |
||
310 | buf[6] = bin2bcd(alarm->time.tm_year); |
||
311 | |||
312 | /* clear alarm interrupt enable bit */ |
||
313 | ret = i2c_smbus_read_byte_data(client, SD2068_CTR2); |
||
314 | if (ret < 0) |
||
315 | goto out; |
||
316 | control = ret; |
||
317 | control &= ~(SD2068_CTR2_INTAE); |
||
318 | ret = i2c_smbus_write_byte_data(client, SD2068_CTR2, control); |
||
319 | if (ret < 0) |
||
320 | goto out; |
||
321 | |||
322 | ret = i2c_smbus_write_i2c_block_data(client, SD2068_ALARM, 7, buf); |
||
323 | |||
324 | ret = i2c_smbus_write_byte_data(client, SD2068_ALARM_EN, 0x7f); |
||
325 | |||
326 | if (alarm->enabled) { |
||
327 | control |= SD2068_CTR2_INTAE; |
||
328 | ret = i2c_smbus_write_byte_data(client, SD2068_CTR2, control); |
||
329 | } |
||
330 | out: |
||
331 | sd2068_write_disable(client); |
||
332 | mutex_unlock(&sd2068->mutex); |
||
333 | return ret; |
||
334 | } |
||
335 | |||
336 | static int sd2068_alarm_irq_enable(struct device *dev, unsigned int enabled) |
||
337 | { |
||
338 | struct i2c_client *client = to_i2c_client(dev); |
||
339 | struct sd2068 *sd2068 = i2c_get_clientdata(client); |
||
340 | unsigned char control; |
||
341 | |||
342 | pr_debug("%s: aie=%d\n", __func__, enabled); |
||
343 | |||
344 | if (client->irq <= 0) |
||
345 | return -EINVAL; |
||
346 | |||
347 | sd2068_write_enable(client); |
||
348 | |||
349 | control = i2c_smbus_read_byte_data(client, SD2068_CTR2); |
||
350 | |||
351 | if (enabled) { |
||
352 | sd2068->rtc->irq_data |= RTC_AF; |
||
353 | control |= SD2068_CTR2_INTAE; |
||
354 | i2c_smbus_write_byte_data(client, SD2068_CTR2, control); |
||
355 | } else { |
||
356 | sd2068->rtc->irq_data &= ~RTC_AF; |
||
357 | control &= ~SD2068_CTR2_INTAE; |
||
358 | i2c_smbus_write_byte_data(client, SD2068_CTR2, control); |
||
359 | } |
||
360 | |||
361 | sd2068_write_disable(client); |
||
362 | |||
363 | return 0; |
||
364 | } |
||
365 | |||
366 | static irqreturn_t sd2068_irq(int irq, void *dev_id) |
||
367 | { |
||
368 | struct i2c_client *client = dev_id; |
||
369 | struct sd2068 *sd2068 = i2c_get_clientdata(client); |
||
370 | |||
371 | disable_irq_nosync(irq); |
||
372 | schedule_work(&sd2068->work); |
||
373 | return IRQ_HANDLED; |
||
374 | } |
||
375 | |||
376 | static void sd2068_work(struct work_struct *work) |
||
377 | { |
||
378 | struct sd2068 *sd2068 = container_of(work, struct sd2068, work); |
||
379 | struct i2c_client *client = sd2068->client; |
||
380 | |||
381 | mutex_lock(&sd2068->mutex); |
||
382 | |||
383 | rtc_update_irq(sd2068->rtc, 1, RTC_AF | RTC_IRQF); |
||
384 | |||
385 | if (!sd2068->exiting) |
||
386 | enable_irq(client->irq); |
||
387 | |||
388 | mutex_unlock(&sd2068->mutex); |
||
389 | } |
||
390 | |||
391 | static const struct rtc_class_ops sd2068_rtc_ops = { |
||
392 | .read_time = sd2068_read_time, |
||
393 | .set_time = sd2068_set_time, |
||
394 | .read_alarm = sd2068_read_alarm, |
||
395 | .set_alarm = sd2068_set_alarm, |
||
396 | .alarm_irq_enable = sd2068_alarm_irq_enable, |
||
397 | }; |
||
398 | |||
399 | static int sd2068_probe(struct i2c_client *client, |
||
400 | const struct i2c_device_id *id) |
||
401 | { |
||
402 | struct sd2068 *sd2068; |
||
403 | struct rtc_time rtc_tm; |
||
404 | int ret; |
||
405 | |||
406 | sd2068 = kzalloc(sizeof(struct sd2068), GFP_KERNEL); |
||
407 | if (!sd2068) |
||
408 | return -ENOMEM; |
||
409 | |||
410 | sd2068->client = client; |
||
411 | i2c_set_clientdata(client, sd2068); |
||
412 | |||
413 | INIT_WORK(&sd2068->work, sd2068_work); |
||
414 | mutex_init(&sd2068->mutex); |
||
415 | |||
416 | sd2068->rtc = rtc_device_register(client->name, &client->dev, |
||
417 | &sd2068_rtc_ops, THIS_MODULE); |
||
418 | if (IS_ERR(sd2068->rtc)) { |
||
419 | ret = PTR_ERR(sd2068->rtc); |
||
420 | dev_err(&client->dev, "unable to register the class device\n"); |
||
421 | goto out_irq; |
||
422 | } |
||
423 | |||
424 | if (client->irq >= 0) { |
||
425 | ret = request_irq(client->irq, sd2068_irq, 0, |
||
426 | "sd2068", client); |
||
427 | if (ret) { |
||
428 | dev_err(&client->dev, "unable to request IRQ\n"); |
||
429 | goto out_free; |
||
430 | } |
||
431 | } |
||
432 | |||
433 | sd2068_hw_init(client); |
||
434 | |||
435 | /* Check RTC Time */ |
||
436 | sd2068_read_time(&client->dev, &rtc_tm); |
||
437 | |||
438 | if (rtc_valid_tm(&rtc_tm)) { |
||
439 | rtc_tm.tm_year = 100; |
||
440 | rtc_tm.tm_mon = 0; |
||
441 | rtc_tm.tm_mday = 1; |
||
442 | rtc_tm.tm_hour = 0; |
||
443 | rtc_tm.tm_min = 0; |
||
444 | rtc_tm.tm_sec = 0; |
||
445 | |||
446 | sd2068_set_time(&client->dev, &rtc_tm); |
||
447 | |||
448 | dev_warn(&client->dev, "warning: invalid RTC value so initializing it\n"); |
||
449 | } |
||
450 | |||
451 | return 0; |
||
452 | |||
453 | out_irq: |
||
454 | if (client->irq >= 0) |
||
455 | free_irq(client->irq, client); |
||
456 | |||
457 | out_free: |
||
458 | kfree(sd2068); |
||
459 | return ret; |
||
460 | } |
||
461 | |||
462 | static int sd2068_remove(struct i2c_client *client) |
||
463 | { |
||
464 | struct sd2068 *sd2068 = i2c_get_clientdata(client); |
||
465 | |||
466 | if (client->irq >= 0) { |
||
467 | mutex_lock(&sd2068->mutex); |
||
468 | sd2068->exiting = 1; |
||
469 | mutex_unlock(&sd2068->mutex); |
||
470 | |||
471 | free_irq(client->irq, client); |
||
472 | cancel_work_sync(&sd2068->work); |
||
473 | } |
||
474 | |||
475 | rtc_device_unregister(sd2068->rtc); |
||
476 | kfree(sd2068); |
||
477 | return 0; |
||
478 | } |
||
479 | |||
480 | static const struct i2c_device_id sd2068_id[] = { |
||
481 | { "sd2068", 0 }, |
||
482 | { } |
||
483 | }; |
||
484 | MODULE_DEVICE_TABLE(i2c, sd2068_id); |
||
485 | |||
486 | static struct i2c_driver sd2068_driver = { |
||
487 | .driver = { |
||
488 | .name = "rtc-sd2068", |
||
489 | .owner = THIS_MODULE, |
||
490 | }, |
||
491 | .probe = sd2068_probe, |
||
492 | .remove = sd2068_remove, |
||
493 | .id_table = sd2068_id, |
||
494 | }; |
||
495 | |||
496 | static int __init sd2068_init(void) |
||
497 | { |
||
498 | return i2c_add_driver(&sd2068_driver); |
||
499 | } |
||
500 | |||
501 | static void __exit sd2068_exit(void) |
||
502 | { |
||
503 | i2c_del_driver(&sd2068_driver); |
||
504 | } |
||
505 | |||
506 | module_init(sd2068_init); |
||
507 | module_exit(sd2068_exit); |
||
508 | |||
509 | MODULE_AUTHOR("Loongson-gz <tanghaifeng-gz@loongson.cn>"); |
||
510 | MODULE_DESCRIPTION("Whwave SD2068 RTC Driver"); |
||
511 | MODULE_LICENSE("GPL"); |