| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 1 | /* | 
 | 2 |  * An rtc driver for the Dallas DS1511 | 
 | 3 |  * | 
 | 4 |  * Copyright (C) 2006 Atsushi Nemoto <anemo@mba.ocn.ne.jp> | 
| Andrew Sharp | 5b73a41 | 2009-09-23 16:03:20 -0700 | [diff] [blame] | 5 |  * Copyright (C) 2007 Andrew Sharp <andy.sharp@lsi.com> | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 6 |  * | 
 | 7 |  * This program is free software; you can redistribute it and/or modify | 
 | 8 |  * it under the terms of the GNU General Public License version 2 as | 
 | 9 |  * published by the Free Software Foundation. | 
 | 10 |  * | 
 | 11 |  * Real time clock driver for the Dallas 1511 chip, which also | 
 | 12 |  * contains a watchdog timer.  There is a tiny amount of code that | 
 | 13 |  * platform code could use to mess with the watchdog device a little | 
 | 14 |  * bit, but not a full watchdog driver. | 
 | 15 |  */ | 
 | 16 |  | 
 | 17 | #include <linux/bcd.h> | 
 | 18 | #include <linux/init.h> | 
 | 19 | #include <linux/kernel.h> | 
| Tejun Heo | 5a0e3ad | 2010-03-24 17:04:11 +0900 | [diff] [blame] | 20 | #include <linux/gfp.h> | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 21 | #include <linux/delay.h> | 
 | 22 | #include <linux/interrupt.h> | 
 | 23 | #include <linux/rtc.h> | 
 | 24 | #include <linux/platform_device.h> | 
 | 25 | #include <linux/io.h> | 
 | 26 |  | 
 | 27 | #define DRV_VERSION "0.6" | 
 | 28 |  | 
 | 29 | enum ds1511reg { | 
 | 30 | 	DS1511_SEC = 0x0, | 
 | 31 | 	DS1511_MIN = 0x1, | 
 | 32 | 	DS1511_HOUR = 0x2, | 
 | 33 | 	DS1511_DOW = 0x3, | 
 | 34 | 	DS1511_DOM = 0x4, | 
 | 35 | 	DS1511_MONTH = 0x5, | 
 | 36 | 	DS1511_YEAR = 0x6, | 
 | 37 | 	DS1511_CENTURY = 0x7, | 
 | 38 | 	DS1511_AM1_SEC = 0x8, | 
 | 39 | 	DS1511_AM2_MIN = 0x9, | 
 | 40 | 	DS1511_AM3_HOUR = 0xa, | 
 | 41 | 	DS1511_AM4_DATE = 0xb, | 
 | 42 | 	DS1511_WD_MSEC = 0xc, | 
 | 43 | 	DS1511_WD_SEC = 0xd, | 
 | 44 | 	DS1511_CONTROL_A = 0xe, | 
 | 45 | 	DS1511_CONTROL_B = 0xf, | 
 | 46 | 	DS1511_RAMADDR_LSB = 0x10, | 
 | 47 | 	DS1511_RAMDATA = 0x13 | 
 | 48 | }; | 
 | 49 |  | 
 | 50 | #define DS1511_BLF1	0x80 | 
 | 51 | #define DS1511_BLF2	0x40 | 
 | 52 | #define DS1511_PRS	0x20 | 
 | 53 | #define DS1511_PAB	0x10 | 
 | 54 | #define DS1511_TDF	0x08 | 
 | 55 | #define DS1511_KSF	0x04 | 
 | 56 | #define DS1511_WDF	0x02 | 
 | 57 | #define DS1511_IRQF	0x01 | 
 | 58 | #define DS1511_TE	0x80 | 
 | 59 | #define DS1511_CS	0x40 | 
 | 60 | #define DS1511_BME	0x20 | 
 | 61 | #define DS1511_TPE	0x10 | 
 | 62 | #define DS1511_TIE	0x08 | 
 | 63 | #define DS1511_KIE	0x04 | 
 | 64 | #define DS1511_WDE	0x02 | 
 | 65 | #define DS1511_WDS	0x01 | 
 | 66 | #define DS1511_RAM_MAX	0xff | 
 | 67 |  | 
 | 68 | #define RTC_CMD		DS1511_CONTROL_B | 
 | 69 | #define RTC_CMD1	DS1511_CONTROL_A | 
 | 70 |  | 
 | 71 | #define RTC_ALARM_SEC	DS1511_AM1_SEC | 
 | 72 | #define RTC_ALARM_MIN	DS1511_AM2_MIN | 
 | 73 | #define RTC_ALARM_HOUR	DS1511_AM3_HOUR | 
 | 74 | #define RTC_ALARM_DATE	DS1511_AM4_DATE | 
 | 75 |  | 
 | 76 | #define RTC_SEC		DS1511_SEC | 
 | 77 | #define RTC_MIN		DS1511_MIN | 
 | 78 | #define RTC_HOUR	DS1511_HOUR | 
 | 79 | #define RTC_DOW		DS1511_DOW | 
 | 80 | #define RTC_DOM		DS1511_DOM | 
 | 81 | #define RTC_MON		DS1511_MONTH | 
 | 82 | #define RTC_YEAR	DS1511_YEAR | 
 | 83 | #define RTC_CENTURY	DS1511_CENTURY | 
 | 84 |  | 
 | 85 | #define RTC_TIE	DS1511_TIE | 
 | 86 | #define RTC_TE	DS1511_TE | 
 | 87 |  | 
 | 88 | struct rtc_plat_data { | 
 | 89 | 	struct rtc_device *rtc; | 
 | 90 | 	void __iomem *ioaddr;		/* virtual base address */ | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 91 | 	int size;				/* amount of memory mapped */ | 
 | 92 | 	int irq; | 
 | 93 | 	unsigned int irqen; | 
 | 94 | 	int alrm_sec; | 
 | 95 | 	int alrm_min; | 
 | 96 | 	int alrm_hour; | 
 | 97 | 	int alrm_mday; | 
| Atsushi Nemoto | ba4f3e4 | 2009-12-15 16:45:58 -0800 | [diff] [blame] | 98 | 	spinlock_t lock; | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 99 | }; | 
 | 100 |  | 
 | 101 | static DEFINE_SPINLOCK(ds1511_lock); | 
 | 102 |  | 
 | 103 | static __iomem char *ds1511_base; | 
 | 104 | static u32 reg_spacing = 1; | 
 | 105 |  | 
 | 106 |  static noinline void | 
 | 107 | rtc_write(uint8_t val, uint32_t reg) | 
 | 108 | { | 
 | 109 | 	writeb(val, ds1511_base + (reg * reg_spacing)); | 
 | 110 | } | 
 | 111 |  | 
 | 112 |  static inline void | 
 | 113 | rtc_write_alarm(uint8_t val, enum ds1511reg reg) | 
 | 114 | { | 
 | 115 | 	rtc_write((val | 0x80), reg); | 
 | 116 | } | 
 | 117 |  | 
 | 118 |  static noinline uint8_t | 
 | 119 | rtc_read(enum ds1511reg reg) | 
 | 120 | { | 
 | 121 | 	return readb(ds1511_base + (reg * reg_spacing)); | 
 | 122 | } | 
 | 123 |  | 
 | 124 |  static inline void | 
 | 125 | rtc_disable_update(void) | 
 | 126 | { | 
 | 127 | 	rtc_write((rtc_read(RTC_CMD) & ~RTC_TE), RTC_CMD); | 
 | 128 | } | 
 | 129 |  | 
 | 130 |  static void | 
 | 131 | rtc_enable_update(void) | 
 | 132 | { | 
 | 133 | 	rtc_write((rtc_read(RTC_CMD) | RTC_TE), RTC_CMD); | 
 | 134 | } | 
 | 135 |  | 
 | 136 | /* | 
 | 137 |  * #define DS1511_WDOG_RESET_SUPPORT | 
 | 138 |  * | 
 | 139 |  * Uncomment this if you want to use these routines in | 
 | 140 |  * some platform code. | 
 | 141 |  */ | 
 | 142 | #ifdef DS1511_WDOG_RESET_SUPPORT | 
 | 143 | /* | 
 | 144 |  * just enough code to set the watchdog timer so that it | 
 | 145 |  * will reboot the system | 
 | 146 |  */ | 
 | 147 |  void | 
 | 148 | ds1511_wdog_set(unsigned long deciseconds) | 
 | 149 | { | 
 | 150 | 	/* | 
 | 151 | 	 * the wdog timer can take 99.99 seconds | 
 | 152 | 	 */ | 
 | 153 | 	deciseconds %= 10000; | 
 | 154 | 	/* | 
 | 155 | 	 * set the wdog values in the wdog registers | 
 | 156 | 	 */ | 
| Adrian Bunk | fe20ba7 | 2008-10-18 20:28:41 -0700 | [diff] [blame] | 157 | 	rtc_write(bin2bcd(deciseconds % 100), DS1511_WD_MSEC); | 
 | 158 | 	rtc_write(bin2bcd(deciseconds / 100), DS1511_WD_SEC); | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 159 | 	/* | 
 | 160 | 	 * set wdog enable and wdog 'steering' bit to issue a reset | 
 | 161 | 	 */ | 
 | 162 | 	rtc_write(DS1511_WDE | DS1511_WDS, RTC_CMD); | 
 | 163 | } | 
 | 164 |  | 
 | 165 |  void | 
 | 166 | ds1511_wdog_disable(void) | 
 | 167 | { | 
 | 168 | 	/* | 
 | 169 | 	 * clear wdog enable and wdog 'steering' bits | 
 | 170 | 	 */ | 
 | 171 | 	rtc_write(rtc_read(RTC_CMD) & ~(DS1511_WDE | DS1511_WDS), RTC_CMD); | 
 | 172 | 	/* | 
 | 173 | 	 * clear the wdog counter | 
 | 174 | 	 */ | 
 | 175 | 	rtc_write(0, DS1511_WD_MSEC); | 
 | 176 | 	rtc_write(0, DS1511_WD_SEC); | 
 | 177 | } | 
 | 178 | #endif | 
 | 179 |  | 
 | 180 | /* | 
 | 181 |  * set the rtc chip's idea of the time. | 
 | 182 |  * stupidly, some callers call with year unmolested; | 
 | 183 |  * and some call with  year = year - 1900.  thanks. | 
 | 184 |  */ | 
| Adrian Bunk | a3ed107 | 2008-04-28 02:11:55 -0700 | [diff] [blame] | 185 | static int ds1511_rtc_set_time(struct device *dev, struct rtc_time *rtc_tm) | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 186 | { | 
 | 187 | 	u8 mon, day, dow, hrs, min, sec, yrs, cen; | 
| Steven Rostedt | 9a0f4ae | 2008-05-06 20:42:30 -0700 | [diff] [blame] | 188 | 	unsigned long flags; | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 189 |  | 
 | 190 | 	/* | 
 | 191 | 	 * won't have to change this for a while | 
 | 192 | 	 */ | 
 | 193 | 	if (rtc_tm->tm_year < 1900) { | 
 | 194 | 		rtc_tm->tm_year += 1900; | 
 | 195 | 	} | 
 | 196 |  | 
 | 197 | 	if (rtc_tm->tm_year < 1970) { | 
 | 198 | 		return -EINVAL; | 
 | 199 | 	} | 
 | 200 | 	yrs = rtc_tm->tm_year % 100; | 
 | 201 | 	cen = rtc_tm->tm_year / 100; | 
 | 202 | 	mon = rtc_tm->tm_mon + 1;   /* tm_mon starts at zero */ | 
 | 203 | 	day = rtc_tm->tm_mday; | 
 | 204 | 	dow = rtc_tm->tm_wday & 0x7; /* automatic BCD */ | 
 | 205 | 	hrs = rtc_tm->tm_hour; | 
 | 206 | 	min = rtc_tm->tm_min; | 
 | 207 | 	sec = rtc_tm->tm_sec; | 
 | 208 |  | 
 | 209 | 	if ((mon > 12) || (day == 0)) { | 
 | 210 | 		return -EINVAL; | 
 | 211 | 	} | 
 | 212 |  | 
 | 213 | 	if (day > rtc_month_days(rtc_tm->tm_mon, rtc_tm->tm_year)) { | 
 | 214 | 		return -EINVAL; | 
 | 215 | 	} | 
 | 216 |  | 
 | 217 | 	if ((hrs >= 24) || (min >= 60) || (sec >= 60)) { | 
 | 218 | 		return -EINVAL; | 
 | 219 | 	} | 
 | 220 |  | 
 | 221 | 	/* | 
 | 222 | 	 * each register is a different number of valid bits | 
 | 223 | 	 */ | 
| Adrian Bunk | fe20ba7 | 2008-10-18 20:28:41 -0700 | [diff] [blame] | 224 | 	sec = bin2bcd(sec) & 0x7f; | 
 | 225 | 	min = bin2bcd(min) & 0x7f; | 
 | 226 | 	hrs = bin2bcd(hrs) & 0x3f; | 
 | 227 | 	day = bin2bcd(day) & 0x3f; | 
 | 228 | 	mon = bin2bcd(mon) & 0x1f; | 
 | 229 | 	yrs = bin2bcd(yrs) & 0xff; | 
 | 230 | 	cen = bin2bcd(cen) & 0xff; | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 231 |  | 
 | 232 | 	spin_lock_irqsave(&ds1511_lock, flags); | 
 | 233 | 	rtc_disable_update(); | 
 | 234 | 	rtc_write(cen, RTC_CENTURY); | 
 | 235 | 	rtc_write(yrs, RTC_YEAR); | 
 | 236 | 	rtc_write((rtc_read(RTC_MON) & 0xe0) | mon, RTC_MON); | 
 | 237 | 	rtc_write(day, RTC_DOM); | 
 | 238 | 	rtc_write(hrs, RTC_HOUR); | 
 | 239 | 	rtc_write(min, RTC_MIN); | 
 | 240 | 	rtc_write(sec, RTC_SEC); | 
 | 241 | 	rtc_write(dow, RTC_DOW); | 
 | 242 | 	rtc_enable_update(); | 
 | 243 | 	spin_unlock_irqrestore(&ds1511_lock, flags); | 
 | 244 |  | 
 | 245 | 	return 0; | 
 | 246 | } | 
 | 247 |  | 
| Adrian Bunk | a3ed107 | 2008-04-28 02:11:55 -0700 | [diff] [blame] | 248 | static int ds1511_rtc_read_time(struct device *dev, struct rtc_time *rtc_tm) | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 249 | { | 
 | 250 | 	unsigned int century; | 
| Steven Rostedt | 9a0f4ae | 2008-05-06 20:42:30 -0700 | [diff] [blame] | 251 | 	unsigned long flags; | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 252 |  | 
 | 253 | 	spin_lock_irqsave(&ds1511_lock, flags); | 
 | 254 | 	rtc_disable_update(); | 
 | 255 |  | 
 | 256 | 	rtc_tm->tm_sec = rtc_read(RTC_SEC) & 0x7f; | 
 | 257 | 	rtc_tm->tm_min = rtc_read(RTC_MIN) & 0x7f; | 
 | 258 | 	rtc_tm->tm_hour = rtc_read(RTC_HOUR) & 0x3f; | 
 | 259 | 	rtc_tm->tm_mday = rtc_read(RTC_DOM) & 0x3f; | 
 | 260 | 	rtc_tm->tm_wday = rtc_read(RTC_DOW) & 0x7; | 
 | 261 | 	rtc_tm->tm_mon = rtc_read(RTC_MON) & 0x1f; | 
 | 262 | 	rtc_tm->tm_year = rtc_read(RTC_YEAR) & 0x7f; | 
 | 263 | 	century = rtc_read(RTC_CENTURY); | 
 | 264 |  | 
 | 265 | 	rtc_enable_update(); | 
 | 266 | 	spin_unlock_irqrestore(&ds1511_lock, flags); | 
 | 267 |  | 
| Adrian Bunk | fe20ba7 | 2008-10-18 20:28:41 -0700 | [diff] [blame] | 268 | 	rtc_tm->tm_sec = bcd2bin(rtc_tm->tm_sec); | 
 | 269 | 	rtc_tm->tm_min = bcd2bin(rtc_tm->tm_min); | 
 | 270 | 	rtc_tm->tm_hour = bcd2bin(rtc_tm->tm_hour); | 
 | 271 | 	rtc_tm->tm_mday = bcd2bin(rtc_tm->tm_mday); | 
 | 272 | 	rtc_tm->tm_wday = bcd2bin(rtc_tm->tm_wday); | 
 | 273 | 	rtc_tm->tm_mon = bcd2bin(rtc_tm->tm_mon); | 
 | 274 | 	rtc_tm->tm_year = bcd2bin(rtc_tm->tm_year); | 
 | 275 | 	century = bcd2bin(century) * 100; | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 276 |  | 
 | 277 | 	/* | 
 | 278 | 	 * Account for differences between how the RTC uses the values | 
 | 279 | 	 * and how they are defined in a struct rtc_time; | 
 | 280 | 	 */ | 
 | 281 | 	century += rtc_tm->tm_year; | 
 | 282 | 	rtc_tm->tm_year = century - 1900; | 
 | 283 |  | 
 | 284 | 	rtc_tm->tm_mon--; | 
 | 285 |  | 
 | 286 | 	if (rtc_valid_tm(rtc_tm) < 0) { | 
 | 287 | 		dev_err(dev, "retrieved date/time is not valid.\n"); | 
 | 288 | 		rtc_time_to_tm(0, rtc_tm); | 
 | 289 | 	} | 
 | 290 | 	return 0; | 
 | 291 | } | 
 | 292 |  | 
 | 293 | /* | 
 | 294 |  * write the alarm register settings | 
 | 295 |  * | 
 | 296 |  * we only have the use to interrupt every second, otherwise | 
 | 297 |  * known as the update interrupt, or the interrupt if the whole | 
 | 298 |  * date/hours/mins/secs matches.  the ds1511 has many more | 
 | 299 |  * permutations, but the kernel doesn't. | 
 | 300 |  */ | 
 | 301 |  static void | 
 | 302 | ds1511_rtc_update_alarm(struct rtc_plat_data *pdata) | 
 | 303 | { | 
 | 304 | 	unsigned long flags; | 
 | 305 |  | 
| Atsushi Nemoto | ba4f3e4 | 2009-12-15 16:45:58 -0800 | [diff] [blame] | 306 | 	spin_lock_irqsave(&pdata->lock, flags); | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 307 | 	rtc_write(pdata->alrm_mday < 0 || (pdata->irqen & RTC_UF) ? | 
| Adrian Bunk | fe20ba7 | 2008-10-18 20:28:41 -0700 | [diff] [blame] | 308 | 	       0x80 : bin2bcd(pdata->alrm_mday) & 0x3f, | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 309 | 	       RTC_ALARM_DATE); | 
 | 310 | 	rtc_write(pdata->alrm_hour < 0 || (pdata->irqen & RTC_UF) ? | 
| Adrian Bunk | fe20ba7 | 2008-10-18 20:28:41 -0700 | [diff] [blame] | 311 | 	       0x80 : bin2bcd(pdata->alrm_hour) & 0x3f, | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 312 | 	       RTC_ALARM_HOUR); | 
 | 313 | 	rtc_write(pdata->alrm_min < 0 || (pdata->irqen & RTC_UF) ? | 
| Adrian Bunk | fe20ba7 | 2008-10-18 20:28:41 -0700 | [diff] [blame] | 314 | 	       0x80 : bin2bcd(pdata->alrm_min) & 0x7f, | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 315 | 	       RTC_ALARM_MIN); | 
 | 316 | 	rtc_write(pdata->alrm_sec < 0 || (pdata->irqen & RTC_UF) ? | 
| Adrian Bunk | fe20ba7 | 2008-10-18 20:28:41 -0700 | [diff] [blame] | 317 | 	       0x80 : bin2bcd(pdata->alrm_sec) & 0x7f, | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 318 | 	       RTC_ALARM_SEC); | 
 | 319 | 	rtc_write(rtc_read(RTC_CMD) | (pdata->irqen ? RTC_TIE : 0), RTC_CMD); | 
 | 320 | 	rtc_read(RTC_CMD1);	/* clear interrupts */ | 
| Atsushi Nemoto | ba4f3e4 | 2009-12-15 16:45:58 -0800 | [diff] [blame] | 321 | 	spin_unlock_irqrestore(&pdata->lock, flags); | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 322 | } | 
 | 323 |  | 
 | 324 |  static int | 
 | 325 | ds1511_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) | 
 | 326 | { | 
 | 327 | 	struct platform_device *pdev = to_platform_device(dev); | 
 | 328 | 	struct rtc_plat_data *pdata = platform_get_drvdata(pdev); | 
 | 329 |  | 
| Anton Vorontsov | 2fac667 | 2009-01-06 14:42:11 -0800 | [diff] [blame] | 330 | 	if (pdata->irq <= 0) | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 331 | 		return -EINVAL; | 
| Anton Vorontsov | 2fac667 | 2009-01-06 14:42:11 -0800 | [diff] [blame] | 332 |  | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 333 | 	pdata->alrm_mday = alrm->time.tm_mday; | 
 | 334 | 	pdata->alrm_hour = alrm->time.tm_hour; | 
 | 335 | 	pdata->alrm_min = alrm->time.tm_min; | 
 | 336 | 	pdata->alrm_sec = alrm->time.tm_sec; | 
 | 337 | 	if (alrm->enabled) { | 
 | 338 | 		pdata->irqen |= RTC_AF; | 
 | 339 | 	} | 
 | 340 | 	ds1511_rtc_update_alarm(pdata); | 
 | 341 | 	return 0; | 
 | 342 | } | 
 | 343 |  | 
 | 344 |  static int | 
 | 345 | ds1511_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) | 
 | 346 | { | 
 | 347 | 	struct platform_device *pdev = to_platform_device(dev); | 
 | 348 | 	struct rtc_plat_data *pdata = platform_get_drvdata(pdev); | 
 | 349 |  | 
| Anton Vorontsov | 2fac667 | 2009-01-06 14:42:11 -0800 | [diff] [blame] | 350 | 	if (pdata->irq <= 0) | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 351 | 		return -EINVAL; | 
| Anton Vorontsov | 2fac667 | 2009-01-06 14:42:11 -0800 | [diff] [blame] | 352 |  | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 353 | 	alrm->time.tm_mday = pdata->alrm_mday < 0 ? 0 : pdata->alrm_mday; | 
 | 354 | 	alrm->time.tm_hour = pdata->alrm_hour < 0 ? 0 : pdata->alrm_hour; | 
 | 355 | 	alrm->time.tm_min = pdata->alrm_min < 0 ? 0 : pdata->alrm_min; | 
 | 356 | 	alrm->time.tm_sec = pdata->alrm_sec < 0 ? 0 : pdata->alrm_sec; | 
 | 357 | 	alrm->enabled = (pdata->irqen & RTC_AF) ? 1 : 0; | 
 | 358 | 	return 0; | 
 | 359 | } | 
 | 360 |  | 
 | 361 |  static irqreturn_t | 
 | 362 | ds1511_interrupt(int irq, void *dev_id) | 
 | 363 | { | 
 | 364 | 	struct platform_device *pdev = dev_id; | 
 | 365 | 	struct rtc_plat_data *pdata = platform_get_drvdata(pdev); | 
| Atsushi Nemoto | ba4f3e4 | 2009-12-15 16:45:58 -0800 | [diff] [blame] | 366 | 	unsigned long events = 0; | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 367 |  | 
| Atsushi Nemoto | ba4f3e4 | 2009-12-15 16:45:58 -0800 | [diff] [blame] | 368 | 	spin_lock(&pdata->lock); | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 369 | 	/* | 
 | 370 | 	 * read and clear interrupt | 
 | 371 | 	 */ | 
| Atsushi Nemoto | ba4f3e4 | 2009-12-15 16:45:58 -0800 | [diff] [blame] | 372 | 	if (rtc_read(RTC_CMD1) & DS1511_IRQF) { | 
 | 373 | 		events = RTC_IRQF; | 
 | 374 | 		if (rtc_read(RTC_ALARM_SEC) & 0x80) | 
 | 375 | 			events |= RTC_UF; | 
 | 376 | 		else | 
 | 377 | 			events |= RTC_AF; | 
 | 378 | 		if (likely(pdata->rtc)) | 
 | 379 | 			rtc_update_irq(pdata->rtc, 1, events); | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 380 | 	} | 
| Atsushi Nemoto | ba4f3e4 | 2009-12-15 16:45:58 -0800 | [diff] [blame] | 381 | 	spin_unlock(&pdata->lock); | 
 | 382 | 	return events ? IRQ_HANDLED : IRQ_NONE; | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 383 | } | 
 | 384 |  | 
| Atsushi Nemoto | ba4f3e4 | 2009-12-15 16:45:58 -0800 | [diff] [blame] | 385 | static int ds1511_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 386 | { | 
 | 387 | 	struct platform_device *pdev = to_platform_device(dev); | 
 | 388 | 	struct rtc_plat_data *pdata = platform_get_drvdata(pdev); | 
 | 389 |  | 
| Atsushi Nemoto | ba4f3e4 | 2009-12-15 16:45:58 -0800 | [diff] [blame] | 390 | 	if (pdata->irq <= 0) | 
 | 391 | 		return -EINVAL; | 
 | 392 | 	if (enabled) | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 393 | 		pdata->irqen |= RTC_AF; | 
| Atsushi Nemoto | ba4f3e4 | 2009-12-15 16:45:58 -0800 | [diff] [blame] | 394 | 	else | 
 | 395 | 		pdata->irqen &= ~RTC_AF; | 
 | 396 | 	ds1511_rtc_update_alarm(pdata); | 
 | 397 | 	return 0; | 
 | 398 | } | 
 | 399 |  | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 400 | static const struct rtc_class_ops ds1511_rtc_ops = { | 
| Atsushi Nemoto | ba4f3e4 | 2009-12-15 16:45:58 -0800 | [diff] [blame] | 401 | 	.read_time		= ds1511_rtc_read_time, | 
 | 402 | 	.set_time		= ds1511_rtc_set_time, | 
 | 403 | 	.read_alarm		= ds1511_rtc_read_alarm, | 
 | 404 | 	.set_alarm		= ds1511_rtc_set_alarm, | 
 | 405 | 	.alarm_irq_enable	= ds1511_rtc_alarm_irq_enable, | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 406 | }; | 
 | 407 |  | 
 | 408 |  static ssize_t | 
| Chris Wright | 2c3c8be | 2010-05-12 18:28:57 -0700 | [diff] [blame] | 409 | ds1511_nvram_read(struct file *filp, struct kobject *kobj, | 
 | 410 | 		  struct bin_attribute *ba, | 
 | 411 | 		  char *buf, loff_t pos, size_t size) | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 412 | { | 
 | 413 | 	ssize_t count; | 
 | 414 |  | 
 | 415 | 	/* | 
 | 416 | 	 * if count is more than one, turn on "burst" mode | 
 | 417 | 	 * turn it off when you're done | 
 | 418 | 	 */ | 
 | 419 | 	if (size > 1) { | 
 | 420 | 		rtc_write((rtc_read(RTC_CMD) | DS1511_BME), RTC_CMD); | 
 | 421 | 	} | 
 | 422 | 	if (pos > DS1511_RAM_MAX) { | 
 | 423 | 		pos = DS1511_RAM_MAX; | 
 | 424 | 	} | 
 | 425 | 	if (size + pos > DS1511_RAM_MAX + 1) { | 
 | 426 | 		size = DS1511_RAM_MAX - pos + 1; | 
 | 427 | 	} | 
 | 428 | 	rtc_write(pos, DS1511_RAMADDR_LSB); | 
 | 429 | 	for (count = 0; size > 0; count++, size--) { | 
 | 430 | 		*buf++ = rtc_read(DS1511_RAMDATA); | 
 | 431 | 	} | 
 | 432 | 	if (count > 1) { | 
 | 433 | 		rtc_write((rtc_read(RTC_CMD) & ~DS1511_BME), RTC_CMD); | 
 | 434 | 	} | 
 | 435 | 	return count; | 
 | 436 | } | 
 | 437 |  | 
 | 438 |  static ssize_t | 
| Chris Wright | 2c3c8be | 2010-05-12 18:28:57 -0700 | [diff] [blame] | 439 | ds1511_nvram_write(struct file *filp, struct kobject *kobj, | 
 | 440 | 		   struct bin_attribute *bin_attr, | 
 | 441 | 		   char *buf, loff_t pos, size_t size) | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 442 | { | 
 | 443 | 	ssize_t count; | 
 | 444 |  | 
 | 445 | 	/* | 
 | 446 | 	 * if count is more than one, turn on "burst" mode | 
 | 447 | 	 * turn it off when you're done | 
 | 448 | 	 */ | 
 | 449 | 	if (size > 1) { | 
 | 450 | 		rtc_write((rtc_read(RTC_CMD) | DS1511_BME), RTC_CMD); | 
 | 451 | 	} | 
 | 452 | 	if (pos > DS1511_RAM_MAX) { | 
 | 453 | 		pos = DS1511_RAM_MAX; | 
 | 454 | 	} | 
 | 455 | 	if (size + pos > DS1511_RAM_MAX + 1) { | 
 | 456 | 		size = DS1511_RAM_MAX - pos + 1; | 
 | 457 | 	} | 
 | 458 | 	rtc_write(pos, DS1511_RAMADDR_LSB); | 
 | 459 | 	for (count = 0; size > 0; count++, size--) { | 
 | 460 | 		rtc_write(*buf++, DS1511_RAMDATA); | 
 | 461 | 	} | 
 | 462 | 	if (count > 1) { | 
 | 463 | 		rtc_write((rtc_read(RTC_CMD) & ~DS1511_BME), RTC_CMD); | 
 | 464 | 	} | 
 | 465 | 	return count; | 
 | 466 | } | 
 | 467 |  | 
 | 468 | static struct bin_attribute ds1511_nvram_attr = { | 
 | 469 | 	.attr = { | 
 | 470 | 		.name = "nvram", | 
| Vasiliy Kulikov | 49d50fb | 2011-03-22 16:34:53 -0700 | [diff] [blame] | 471 | 		.mode = S_IRUGO | S_IWUSR, | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 472 | 	}, | 
 | 473 | 	.size = DS1511_RAM_MAX, | 
 | 474 | 	.read = ds1511_nvram_read, | 
 | 475 | 	.write = ds1511_nvram_write, | 
 | 476 | }; | 
 | 477 |  | 
 | 478 |  static int __devinit | 
 | 479 | ds1511_rtc_probe(struct platform_device *pdev) | 
 | 480 | { | 
 | 481 | 	struct rtc_device *rtc; | 
 | 482 | 	struct resource *res; | 
| Atsushi Nemoto | ba4f3e4 | 2009-12-15 16:45:58 -0800 | [diff] [blame] | 483 | 	struct rtc_plat_data *pdata; | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 484 | 	int ret = 0; | 
 | 485 |  | 
 | 486 | 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
 | 487 | 	if (!res) { | 
 | 488 | 		return -ENODEV; | 
 | 489 | 	} | 
| Atsushi Nemoto | ba4f3e4 | 2009-12-15 16:45:58 -0800 | [diff] [blame] | 490 | 	pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); | 
 | 491 | 	if (!pdata) | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 492 | 		return -ENOMEM; | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 493 | 	pdata->size = res->end - res->start + 1; | 
| Atsushi Nemoto | ba4f3e4 | 2009-12-15 16:45:58 -0800 | [diff] [blame] | 494 | 	if (!devm_request_mem_region(&pdev->dev, res->start, pdata->size, | 
 | 495 | 			pdev->name)) | 
 | 496 | 		return -EBUSY; | 
 | 497 | 	ds1511_base = devm_ioremap(&pdev->dev, res->start, pdata->size); | 
 | 498 | 	if (!ds1511_base) | 
 | 499 | 		return -ENOMEM; | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 500 | 	pdata->ioaddr = ds1511_base; | 
 | 501 | 	pdata->irq = platform_get_irq(pdev, 0); | 
 | 502 |  | 
 | 503 | 	/* | 
 | 504 | 	 * turn on the clock and the crystal, etc. | 
 | 505 | 	 */ | 
 | 506 | 	rtc_write(0, RTC_CMD); | 
 | 507 | 	rtc_write(0, RTC_CMD1); | 
 | 508 | 	/* | 
 | 509 | 	 * clear the wdog counter | 
 | 510 | 	 */ | 
 | 511 | 	rtc_write(0, DS1511_WD_MSEC); | 
 | 512 | 	rtc_write(0, DS1511_WD_SEC); | 
 | 513 | 	/* | 
 | 514 | 	 * start the clock | 
 | 515 | 	 */ | 
 | 516 | 	rtc_enable_update(); | 
 | 517 |  | 
 | 518 | 	/* | 
 | 519 | 	 * check for a dying bat-tree | 
 | 520 | 	 */ | 
 | 521 | 	if (rtc_read(RTC_CMD1) & DS1511_BLF1) { | 
 | 522 | 		dev_warn(&pdev->dev, "voltage-low detected.\n"); | 
 | 523 | 	} | 
 | 524 |  | 
| Atsushi Nemoto | ba4f3e4 | 2009-12-15 16:45:58 -0800 | [diff] [blame] | 525 | 	spin_lock_init(&pdata->lock); | 
 | 526 | 	platform_set_drvdata(pdev, pdata); | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 527 | 	/* | 
 | 528 | 	 * if the platform has an interrupt in mind for this device, | 
 | 529 | 	 * then by all means, set it | 
 | 530 | 	 */ | 
| Anton Vorontsov | 2fac667 | 2009-01-06 14:42:11 -0800 | [diff] [blame] | 531 | 	if (pdata->irq > 0) { | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 532 | 		rtc_read(RTC_CMD1); | 
| Atsushi Nemoto | ba4f3e4 | 2009-12-15 16:45:58 -0800 | [diff] [blame] | 533 | 		if (devm_request_irq(&pdev->dev, pdata->irq, ds1511_interrupt, | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 534 | 			IRQF_DISABLED | IRQF_SHARED, pdev->name, pdev) < 0) { | 
 | 535 |  | 
 | 536 | 			dev_warn(&pdev->dev, "interrupt not available.\n"); | 
| Anton Vorontsov | 2fac667 | 2009-01-06 14:42:11 -0800 | [diff] [blame] | 537 | 			pdata->irq = 0; | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 538 | 		} | 
 | 539 | 	} | 
 | 540 |  | 
 | 541 | 	rtc = rtc_device_register(pdev->name, &pdev->dev, &ds1511_rtc_ops, | 
 | 542 | 		THIS_MODULE); | 
| Atsushi Nemoto | ba4f3e4 | 2009-12-15 16:45:58 -0800 | [diff] [blame] | 543 | 	if (IS_ERR(rtc)) | 
 | 544 | 		return PTR_ERR(rtc); | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 545 | 	pdata->rtc = rtc; | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 546 |  | 
| Atsushi Nemoto | ba4f3e4 | 2009-12-15 16:45:58 -0800 | [diff] [blame] | 547 | 	ret = sysfs_create_bin_file(&pdev->dev.kobj, &ds1511_nvram_attr); | 
 | 548 | 	if (ret) | 
 | 549 | 		rtc_device_unregister(pdata->rtc); | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 550 | 	return ret; | 
 | 551 | } | 
 | 552 |  | 
 | 553 |  static int __devexit | 
 | 554 | ds1511_rtc_remove(struct platform_device *pdev) | 
 | 555 | { | 
 | 556 | 	struct rtc_plat_data *pdata = platform_get_drvdata(pdev); | 
 | 557 |  | 
 | 558 | 	sysfs_remove_bin_file(&pdev->dev.kobj, &ds1511_nvram_attr); | 
 | 559 | 	rtc_device_unregister(pdata->rtc); | 
| Anton Vorontsov | 2fac667 | 2009-01-06 14:42:11 -0800 | [diff] [blame] | 560 | 	if (pdata->irq > 0) { | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 561 | 		/* | 
 | 562 | 		 * disable the alarm interrupt | 
 | 563 | 		 */ | 
 | 564 | 		rtc_write(rtc_read(RTC_CMD) & ~RTC_TIE, RTC_CMD); | 
 | 565 | 		rtc_read(RTC_CMD1); | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 566 | 	} | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 567 | 	return 0; | 
 | 568 | } | 
 | 569 |  | 
| Kay Sievers | ad28a07 | 2008-04-10 21:29:25 -0700 | [diff] [blame] | 570 | /* work with hotplug and coldplug */ | 
 | 571 | MODULE_ALIAS("platform:ds1511"); | 
 | 572 |  | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 573 | static struct platform_driver ds1511_rtc_driver = { | 
 | 574 | 	.probe		= ds1511_rtc_probe, | 
 | 575 | 	.remove		= __devexit_p(ds1511_rtc_remove), | 
 | 576 | 	.driver		= { | 
 | 577 | 		.name	= "ds1511", | 
 | 578 | 		.owner	= THIS_MODULE, | 
 | 579 | 	}, | 
 | 580 | }; | 
 | 581 |  | 
 | 582 |  static int __init | 
 | 583 | ds1511_rtc_init(void) | 
 | 584 | { | 
 | 585 | 	return platform_driver_register(&ds1511_rtc_driver); | 
 | 586 | } | 
 | 587 |  | 
 | 588 |  static void __exit | 
 | 589 | ds1511_rtc_exit(void) | 
 | 590 | { | 
| Hannes Eder | 4367fa5 | 2008-11-30 14:05:45 +0100 | [diff] [blame] | 591 | 	platform_driver_unregister(&ds1511_rtc_driver); | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 592 | } | 
 | 593 |  | 
 | 594 | module_init(ds1511_rtc_init); | 
 | 595 | module_exit(ds1511_rtc_exit); | 
 | 596 |  | 
| Andrew Sharp | 5b73a41 | 2009-09-23 16:03:20 -0700 | [diff] [blame] | 597 | MODULE_AUTHOR("Andrew Sharp <andy.sharp@lsi.com>"); | 
| Andrew Sharp | 8f26795 | 2008-02-06 01:38:46 -0800 | [diff] [blame] | 598 | MODULE_DESCRIPTION("Dallas DS1511 RTC driver"); | 
 | 599 | MODULE_LICENSE("GPL"); | 
 | 600 | MODULE_VERSION(DRV_VERSION); |