diff options
author | Jesper Nilsson <jesper.nilsson@axis.com> | 2008-01-24 08:24:09 -0500 |
---|---|---|
committer | Jesper Nilsson <jesper.nilsson@axis.com> | 2008-02-08 05:06:34 -0500 |
commit | 7edf744053873e390d7d05ab0136c5162cf89c27 (patch) | |
tree | 3c6da49d72f829b3a1c782fb2acd9e274b0704b3 /arch/cris/arch-v32/drivers | |
parent | d8ac17a0eeab6580cced355de85ac90227096bb9 (diff) |
CRIS v32: Update driver for RTC chip pcf8563.
- Moved all calls to register_chrdev to a function called by module_init.
- Added mutex locking.
- Added better error handling at start up.
- Added BIN_TO_BCD of the month value before it is saved to the RTC.
- Corrected the month value returned by pcf8563_readreg.
- Cache the voltage low value at driver init so the battery status
information does not get 'accidentally' cleared when setting the RTC time.
- Removed obsolete CONFIG_ETRAX_RTC_READONLY
- Voltage low ioctl():s RTC_VLOW_RD -> RTC_VL_READ, RTC_VLOW_SET -> RTC_VL_CLR
Diffstat (limited to 'arch/cris/arch-v32/drivers')
-rw-r--r-- | arch/cris/arch-v32/drivers/pcf8563.c | 296 |
1 files changed, 163 insertions, 133 deletions
diff --git a/arch/cris/arch-v32/drivers/pcf8563.c b/arch/cris/arch-v32/drivers/pcf8563.c index 6dbd700d3d66..53db3870ba04 100644 --- a/arch/cris/arch-v32/drivers/pcf8563.c +++ b/arch/cris/arch-v32/drivers/pcf8563.c | |||
@@ -10,7 +10,7 @@ | |||
10 | * 400 kbits/s. The built-in word address register is incremented | 10 | * 400 kbits/s. The built-in word address register is incremented |
11 | * automatically after each written or read byte. | 11 | * automatically after each written or read byte. |
12 | * | 12 | * |
13 | * Copyright (c) 2002-2003, Axis Communications AB | 13 | * Copyright (c) 2002-2007, Axis Communications AB |
14 | * All rights reserved. | 14 | * All rights reserved. |
15 | * | 15 | * |
16 | * Author: Tobias Anderberg <tobiasa@axis.com>. | 16 | * Author: Tobias Anderberg <tobiasa@axis.com>. |
@@ -26,6 +26,7 @@ | |||
26 | #include <linux/ioctl.h> | 26 | #include <linux/ioctl.h> |
27 | #include <linux/delay.h> | 27 | #include <linux/delay.h> |
28 | #include <linux/bcd.h> | 28 | #include <linux/bcd.h> |
29 | #include <linux/mutex.h> | ||
29 | 30 | ||
30 | #include <asm/uaccess.h> | 31 | #include <asm/uaccess.h> |
31 | #include <asm/system.h> | 32 | #include <asm/system.h> |
@@ -37,24 +38,27 @@ | |||
37 | #define PCF8563_MAJOR 121 /* Local major number. */ | 38 | #define PCF8563_MAJOR 121 /* Local major number. */ |
38 | #define DEVICE_NAME "rtc" /* Name which is registered in /proc/devices. */ | 39 | #define DEVICE_NAME "rtc" /* Name which is registered in /proc/devices. */ |
39 | #define PCF8563_NAME "PCF8563" | 40 | #define PCF8563_NAME "PCF8563" |
40 | #define DRIVER_VERSION "$Revision: 1.1 $" | 41 | #define DRIVER_VERSION "$Revision: 1.17 $" |
41 | 42 | ||
42 | /* Two simple wrapper macros, saves a few keystrokes. */ | 43 | /* Two simple wrapper macros, saves a few keystrokes. */ |
43 | #define rtc_read(x) i2c_readreg(RTC_I2C_READ, x) | 44 | #define rtc_read(x) i2c_readreg(RTC_I2C_READ, x) |
44 | #define rtc_write(x,y) i2c_writereg(RTC_I2C_WRITE, x, y) | 45 | #define rtc_write(x,y) i2c_writereg(RTC_I2C_WRITE, x, y) |
45 | 46 | ||
47 | static DEFINE_MUTEX(rtc_lock); /* Protect state etc */ | ||
48 | |||
46 | static const unsigned char days_in_month[] = | 49 | static const unsigned char days_in_month[] = |
47 | { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; | 50 | { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; |
48 | 51 | ||
49 | int pcf8563_ioctl(struct inode *, struct file *, unsigned int, unsigned long); | 52 | int pcf8563_ioctl(struct inode *, struct file *, unsigned int, unsigned long); |
50 | int pcf8563_open(struct inode *, struct file *); | 53 | |
51 | int pcf8563_release(struct inode *, struct file *); | 54 | /* Cache VL bit value read at driver init since writing the RTC_SECOND |
55 | * register clears the VL status. | ||
56 | */ | ||
57 | static int voltage_low; | ||
52 | 58 | ||
53 | static const struct file_operations pcf8563_fops = { | 59 | static const struct file_operations pcf8563_fops = { |
54 | .owner = THIS_MODULE, | 60 | .owner = THIS_MODULE, |
55 | .ioctl = pcf8563_ioctl, | 61 | .ioctl = pcf8563_ioctl |
56 | .open = pcf8563_open, | ||
57 | .release = pcf8563_release, | ||
58 | }; | 62 | }; |
59 | 63 | ||
60 | unsigned char | 64 | unsigned char |
@@ -62,7 +66,7 @@ pcf8563_readreg(int reg) | |||
62 | { | 66 | { |
63 | unsigned char res = rtc_read(reg); | 67 | unsigned char res = rtc_read(reg); |
64 | 68 | ||
65 | /* The PCF8563 does not return 0 for unimplemented bits */ | 69 | /* The PCF8563 does not return 0 for unimplemented bits. */ |
66 | switch (reg) { | 70 | switch (reg) { |
67 | case RTC_SECONDS: | 71 | case RTC_SECONDS: |
68 | case RTC_MINUTES: | 72 | case RTC_MINUTES: |
@@ -95,11 +99,6 @@ pcf8563_readreg(int reg) | |||
95 | void | 99 | void |
96 | pcf8563_writereg(int reg, unsigned char val) | 100 | pcf8563_writereg(int reg, unsigned char val) |
97 | { | 101 | { |
98 | #ifdef CONFIG_ETRAX_RTC_READONLY | ||
99 | if (reg == RTC_CONTROL1 || (reg >= RTC_SECONDS && reg <= RTC_YEAR)) | ||
100 | return; | ||
101 | #endif | ||
102 | |||
103 | rtc_write(reg, val); | 102 | rtc_write(reg, val); |
104 | } | 103 | } |
105 | 104 | ||
@@ -114,11 +113,13 @@ get_rtc_time(struct rtc_time *tm) | |||
114 | tm->tm_mon = rtc_read(RTC_MONTH); | 113 | tm->tm_mon = rtc_read(RTC_MONTH); |
115 | tm->tm_year = rtc_read(RTC_YEAR); | 114 | tm->tm_year = rtc_read(RTC_YEAR); |
116 | 115 | ||
117 | if (tm->tm_sec & 0x80) | 116 | if (tm->tm_sec & 0x80) { |
118 | printk(KERN_WARNING "%s: RTC Voltage Low - reliable date/time " | 117 | printk(KERN_ERR "%s: RTC Voltage Low - reliable date/time " |
119 | "information is no longer guaranteed!\n", PCF8563_NAME); | 118 | "information is no longer guaranteed!\n", PCF8563_NAME); |
119 | } | ||
120 | 120 | ||
121 | tm->tm_year = BCD_TO_BIN(tm->tm_year) + ((tm->tm_mon & 0x80) ? 100 : 0); | 121 | tm->tm_year = BCD_TO_BIN(tm->tm_year) + |
122 | ((tm->tm_mon & 0x80) ? 100 : 0); | ||
122 | tm->tm_sec &= 0x7F; | 123 | tm->tm_sec &= 0x7F; |
123 | tm->tm_min &= 0x7F; | 124 | tm->tm_min &= 0x7F; |
124 | tm->tm_hour &= 0x3F; | 125 | tm->tm_hour &= 0x3F; |
@@ -137,8 +138,19 @@ get_rtc_time(struct rtc_time *tm) | |||
137 | int __init | 138 | int __init |
138 | pcf8563_init(void) | 139 | pcf8563_init(void) |
139 | { | 140 | { |
141 | static int res; | ||
142 | static int first = 1; | ||
143 | |||
144 | if (!first) | ||
145 | return res; | ||
146 | first = 0; | ||
147 | |||
140 | /* Initiate the i2c protocol. */ | 148 | /* Initiate the i2c protocol. */ |
141 | i2c_init(); | 149 | res = i2c_init(); |
150 | if (res < 0) { | ||
151 | printk(KERN_CRIT "pcf8563_init: Failed to init i2c.\n"); | ||
152 | return res; | ||
153 | } | ||
142 | 154 | ||
143 | /* | 155 | /* |
144 | * First of all we need to reset the chip. This is done by | 156 | * First of all we need to reset the chip. This is done by |
@@ -170,24 +182,20 @@ pcf8563_init(void) | |||
170 | if (rtc_write(RTC_WEEKDAY_ALARM, 0x80) < 0) | 182 | if (rtc_write(RTC_WEEKDAY_ALARM, 0x80) < 0) |
171 | goto err; | 183 | goto err; |
172 | 184 | ||
173 | if (register_chrdev(PCF8563_MAJOR, DEVICE_NAME, &pcf8563_fops) < 0) { | 185 | /* Check for low voltage, and warn about it. */ |
174 | printk(KERN_INFO "%s: Unable to get major number %d for RTC device.\n", | 186 | if (rtc_read(RTC_SECONDS) & 0x80) { |
175 | PCF8563_NAME, PCF8563_MAJOR); | 187 | voltage_low = 1; |
176 | return -1; | 188 | printk(KERN_WARNING "%s: RTC Voltage Low - reliable " |
189 | "date/time information is no longer guaranteed!\n", | ||
190 | PCF8563_NAME); | ||
177 | } | 191 | } |
178 | 192 | ||
179 | printk(KERN_INFO "%s Real-Time Clock Driver, %s\n", PCF8563_NAME, DRIVER_VERSION); | 193 | return res; |
180 | |||
181 | /* Check for low voltage, and warn about it.. */ | ||
182 | if (rtc_read(RTC_SECONDS) & 0x80) | ||
183 | printk(KERN_WARNING "%s: RTC Voltage Low - reliable date/time " | ||
184 | "information is no longer guaranteed!\n", PCF8563_NAME); | ||
185 | |||
186 | return 0; | ||
187 | 194 | ||
188 | err: | 195 | err: |
189 | printk(KERN_INFO "%s: Error initializing chip.\n", PCF8563_NAME); | 196 | printk(KERN_INFO "%s: Error initializing chip.\n", PCF8563_NAME); |
190 | return -1; | 197 | res = -1; |
198 | return res; | ||
191 | } | 199 | } |
192 | 200 | ||
193 | void __exit | 201 | void __exit |
@@ -200,8 +208,8 @@ pcf8563_exit(void) | |||
200 | * ioctl calls for this driver. Why return -ENOTTY upon error? Because | 208 | * ioctl calls for this driver. Why return -ENOTTY upon error? Because |
201 | * POSIX says so! | 209 | * POSIX says so! |
202 | */ | 210 | */ |
203 | int | 211 | int pcf8563_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, |
204 | pcf8563_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) | 212 | unsigned long arg) |
205 | { | 213 | { |
206 | /* Some sanity checks. */ | 214 | /* Some sanity checks. */ |
207 | if (_IOC_TYPE(cmd) != RTC_MAGIC) | 215 | if (_IOC_TYPE(cmd) != RTC_MAGIC) |
@@ -211,125 +219,147 @@ pcf8563_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned | |||
211 | return -ENOTTY; | 219 | return -ENOTTY; |
212 | 220 | ||
213 | switch (cmd) { | 221 | switch (cmd) { |
214 | case RTC_RD_TIME: | 222 | case RTC_RD_TIME: |
215 | { | 223 | { |
216 | struct rtc_time tm; | 224 | struct rtc_time tm; |
217 | 225 | ||
218 | memset(&tm, 0, sizeof (struct rtc_time)); | 226 | mutex_lock(&rtc_lock); |
219 | get_rtc_time(&tm); | 227 | memset(&tm, 0, sizeof tm); |
220 | 228 | get_rtc_time(&tm); | |
221 | if (copy_to_user((struct rtc_time *) arg, &tm, sizeof tm)) { | 229 | |
222 | return -EFAULT; | 230 | if (copy_to_user((struct rtc_time *) arg, &tm, |
223 | } | 231 | sizeof tm)) { |
224 | 232 | spin_unlock(&rtc_lock); | |
225 | return 0; | 233 | return -EFAULT; |
226 | } | 234 | } |
227 | 235 | ||
228 | case RTC_SET_TIME: | 236 | mutex_unlock(&rtc_lock); |
229 | { | 237 | |
230 | #ifdef CONFIG_ETRAX_RTC_READONLY | 238 | return 0; |
239 | } | ||
240 | case RTC_SET_TIME: | ||
241 | { | ||
242 | int leap; | ||
243 | int year; | ||
244 | int century; | ||
245 | struct rtc_time tm; | ||
246 | |||
247 | memset(&tm, 0, sizeof tm); | ||
248 | if (!capable(CAP_SYS_TIME)) | ||
231 | return -EPERM; | 249 | return -EPERM; |
232 | #else | ||
233 | int leap; | ||
234 | int year; | ||
235 | int century; | ||
236 | struct rtc_time tm; | ||
237 | |||
238 | if (!capable(CAP_SYS_TIME)) | ||
239 | return -EPERM; | ||
240 | |||
241 | if (copy_from_user(&tm, (struct rtc_time *) arg, sizeof tm)) | ||
242 | return -EFAULT; | ||
243 | |||
244 | /* Convert from struct tm to struct rtc_time. */ | ||
245 | tm.tm_year += 1900; | ||
246 | tm.tm_mon += 1; | ||
247 | |||
248 | /* | ||
249 | * Check if tm.tm_year is a leap year. A year is a leap | ||
250 | * year if it is divisible by 4 but not 100, except | ||
251 | * that years divisible by 400 _are_ leap years. | ||
252 | */ | ||
253 | year = tm.tm_year; | ||
254 | leap = (tm.tm_mon == 2) && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0); | ||
255 | |||
256 | /* Perform some sanity checks. */ | ||
257 | if ((tm.tm_year < 1970) || | ||
258 | (tm.tm_mon > 12) || | ||
259 | (tm.tm_mday == 0) || | ||
260 | (tm.tm_mday > days_in_month[tm.tm_mon] + leap) || | ||
261 | (tm.tm_wday >= 7) || | ||
262 | (tm.tm_hour >= 24) || | ||
263 | (tm.tm_min >= 60) || | ||
264 | (tm.tm_sec >= 60)) | ||
265 | return -EINVAL; | ||
266 | |||
267 | century = (tm.tm_year >= 2000) ? 0x80 : 0; | ||
268 | tm.tm_year = tm.tm_year % 100; | ||
269 | |||
270 | BIN_TO_BCD(tm.tm_year); | ||
271 | BIN_TO_BCD(tm.tm_mday); | ||
272 | BIN_TO_BCD(tm.tm_hour); | ||
273 | BIN_TO_BCD(tm.tm_min); | ||
274 | BIN_TO_BCD(tm.tm_sec); | ||
275 | tm.tm_mon |= century; | ||
276 | |||
277 | rtc_write(RTC_YEAR, tm.tm_year); | ||
278 | rtc_write(RTC_MONTH, tm.tm_mon); | ||
279 | rtc_write(RTC_WEEKDAY, tm.tm_wday); /* Not coded in BCD. */ | ||
280 | rtc_write(RTC_DAY_OF_MONTH, tm.tm_mday); | ||
281 | rtc_write(RTC_HOURS, tm.tm_hour); | ||
282 | rtc_write(RTC_MINUTES, tm.tm_min); | ||
283 | rtc_write(RTC_SECONDS, tm.tm_sec); | ||
284 | |||
285 | return 0; | ||
286 | #endif /* !CONFIG_ETRAX_RTC_READONLY */ | ||
287 | } | ||
288 | 250 | ||
289 | case RTC_VLOW_RD: | 251 | if (copy_from_user(&tm, (struct rtc_time *) arg, |
290 | { | 252 | sizeof tm)) |
291 | int vl_bit = 0; | 253 | return -EFAULT; |
254 | |||
255 | /* Convert from struct tm to struct rtc_time. */ | ||
256 | tm.tm_year += 1900; | ||
257 | tm.tm_mon += 1; | ||
258 | |||
259 | /* | ||
260 | * Check if tm.tm_year is a leap year. A year is a leap | ||
261 | * year if it is divisible by 4 but not 100, except | ||
262 | * that years divisible by 400 _are_ leap years. | ||
263 | */ | ||
264 | year = tm.tm_year; | ||
265 | leap = (tm.tm_mon == 2) && | ||
266 | ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0); | ||
267 | |||
268 | /* Perform some sanity checks. */ | ||
269 | if ((tm.tm_year < 1970) || | ||
270 | (tm.tm_mon > 12) || | ||
271 | (tm.tm_mday == 0) || | ||
272 | (tm.tm_mday > days_in_month[tm.tm_mon] + leap) || | ||
273 | (tm.tm_wday >= 7) || | ||
274 | (tm.tm_hour >= 24) || | ||
275 | (tm.tm_min >= 60) || | ||
276 | (tm.tm_sec >= 60)) | ||
277 | return -EINVAL; | ||
278 | |||
279 | century = (tm.tm_year >= 2000) ? 0x80 : 0; | ||
280 | tm.tm_year = tm.tm_year % 100; | ||
281 | |||
282 | BIN_TO_BCD(tm.tm_year); | ||
283 | BIN_TO_BCD(tm.tm_mon); | ||
284 | BIN_TO_BCD(tm.tm_mday); | ||
285 | BIN_TO_BCD(tm.tm_hour); | ||
286 | BIN_TO_BCD(tm.tm_min); | ||
287 | BIN_TO_BCD(tm.tm_sec); | ||
288 | tm.tm_mon |= century; | ||
289 | |||
290 | mutex_lock(&rtc_lock); | ||
291 | |||
292 | rtc_write(RTC_YEAR, tm.tm_year); | ||
293 | rtc_write(RTC_MONTH, tm.tm_mon); | ||
294 | rtc_write(RTC_WEEKDAY, tm.tm_wday); /* Not coded in BCD. */ | ||
295 | rtc_write(RTC_DAY_OF_MONTH, tm.tm_mday); | ||
296 | rtc_write(RTC_HOURS, tm.tm_hour); | ||
297 | rtc_write(RTC_MINUTES, tm.tm_min); | ||
298 | rtc_write(RTC_SECONDS, tm.tm_sec); | ||
299 | |||
300 | mutex_unlock(&rtc_lock); | ||
301 | |||
302 | return 0; | ||
303 | } | ||
304 | case RTC_VL_READ: | ||
305 | if (voltage_low) | ||
306 | printk(KERN_ERR "%s: RTC Voltage Low - " | ||
307 | "reliable date/time information is no " | ||
308 | "longer guaranteed!\n", PCF8563_NAME); | ||
292 | 309 | ||
293 | if (rtc_read(RTC_SECONDS) & 0x80) { | 310 | if (copy_to_user((int *) arg, &voltage_low, sizeof(int))) |
294 | vl_bit = 1; | 311 | return -EFAULT; |
295 | printk(KERN_WARNING "%s: RTC Voltage Low - reliable " | 312 | return 0; |
296 | "date/time information is no longer guaranteed!\n", | ||
297 | PCF8563_NAME); | ||
298 | } | ||
299 | if (copy_to_user((int *) arg, &vl_bit, sizeof(int))) | ||
300 | return -EFAULT; | ||
301 | 313 | ||
302 | return 0; | 314 | case RTC_VL_CLR: |
303 | } | 315 | { |
316 | /* Clear the VL bit in the seconds register in case | ||
317 | * the time has not been set already (which would | ||
318 | * have cleared it). This does not really matter | ||
319 | * because of the cached voltage_low value but do it | ||
320 | * anyway for consistency. */ | ||
304 | 321 | ||
305 | case RTC_VLOW_SET: | 322 | int ret = rtc_read(RTC_SECONDS); |
306 | { | ||
307 | /* Clear the VL bit in the seconds register */ | ||
308 | int ret = rtc_read(RTC_SECONDS); | ||
309 | 323 | ||
310 | rtc_write(RTC_SECONDS, (ret & 0x7F)); | 324 | rtc_write(RTC_SECONDS, (ret & 0x7F)); |
311 | 325 | ||
312 | return 0; | 326 | /* Clear the cached value. */ |
313 | } | 327 | voltage_low = 0; |
314 | 328 | ||
315 | default: | 329 | return 0; |
316 | return -ENOTTY; | 330 | } |
331 | default: | ||
332 | return -ENOTTY; | ||
317 | } | 333 | } |
318 | 334 | ||
319 | return 0; | 335 | return 0; |
320 | } | 336 | } |
321 | 337 | ||
322 | int | 338 | static int __init pcf8563_register(void) |
323 | pcf8563_open(struct inode *inode, struct file *filp) | ||
324 | { | 339 | { |
325 | return 0; | 340 | if (pcf8563_init() < 0) { |
326 | } | 341 | printk(KERN_INFO "%s: Unable to initialize Real-Time Clock " |
342 | "Driver, %s\n", PCF8563_NAME, DRIVER_VERSION); | ||
343 | return -1; | ||
344 | } | ||
345 | |||
346 | if (register_chrdev(PCF8563_MAJOR, DEVICE_NAME, &pcf8563_fops) < 0) { | ||
347 | printk(KERN_INFO "%s: Unable to get major numer %d for RTC " | ||
348 | "device.\n", PCF8563_NAME, PCF8563_MAJOR); | ||
349 | return -1; | ||
350 | } | ||
351 | |||
352 | printk(KERN_INFO "%s Real-Time Clock Driver, %s\n", PCF8563_NAME, | ||
353 | DRIVER_VERSION); | ||
354 | |||
355 | /* Check for low voltage, and warn about it. */ | ||
356 | if (voltage_low) { | ||
357 | printk(KERN_WARNING "%s: RTC Voltage Low - reliable date/time " | ||
358 | "information is no longer guaranteed!\n", PCF8563_NAME); | ||
359 | } | ||
327 | 360 | ||
328 | int | ||
329 | pcf8563_release(struct inode *inode, struct file *filp) | ||
330 | { | ||
331 | return 0; | 361 | return 0; |
332 | } | 362 | } |
333 | 363 | ||
334 | module_init(pcf8563_init); | 364 | module_init(pcf8563_register); |
335 | module_exit(pcf8563_exit); | 365 | module_exit(pcf8563_exit); |