1bf4994d7SScott Wood /* 2bf4994d7SScott Wood * RTC client/driver for the Maxim/Dallas DS1374 Real-Time Clock over I2C 3bf4994d7SScott Wood * 4bf4994d7SScott Wood * Based on code by Randy Vinson <rvinson@mvista.com>, 5bf4994d7SScott Wood * which was based on the m41t00.c by Mark Greer <mgreer@mvista.com>. 6bf4994d7SScott Wood * 7920f91e5SSøren Andersen * Copyright (C) 2014 Rose Technology 8bf4994d7SScott Wood * Copyright (C) 2006-2007 Freescale Semiconductor 9bf4994d7SScott Wood * 10bf4994d7SScott Wood * 2005 (c) MontaVista Software, Inc. This file is licensed under 11bf4994d7SScott Wood * the terms of the GNU General Public License version 2. This program 12bf4994d7SScott Wood * is licensed "as is" without any warranty of any kind, whether express 13bf4994d7SScott Wood * or implied. 14bf4994d7SScott Wood */ 15bf4994d7SScott Wood /* 16bf4994d7SScott Wood * It would be more efficient to use i2c msgs/i2c_transfer directly but, as 17bf4994d7SScott Wood * recommened in .../Documentation/i2c/writing-clients section 18bf4994d7SScott Wood * "Sending and receiving", using SMBus level communication is preferred. 19bf4994d7SScott Wood */ 20bf4994d7SScott Wood 21a737e835SJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 22a737e835SJoe Perches 23bf4994d7SScott Wood #include <linux/kernel.h> 24bf4994d7SScott Wood #include <linux/module.h> 25bf4994d7SScott Wood #include <linux/interrupt.h> 26bf4994d7SScott Wood #include <linux/i2c.h> 27bf4994d7SScott Wood #include <linux/rtc.h> 28bf4994d7SScott Wood #include <linux/bcd.h> 29bf4994d7SScott Wood #include <linux/workqueue.h> 305a0e3ad6STejun Heo #include <linux/slab.h> 31bc96ba74SMark Brown #include <linux/pm.h> 32920f91e5SSøren Andersen #ifdef CONFIG_RTC_DRV_DS1374_WDT 33920f91e5SSøren Andersen #include <linux/fs.h> 34920f91e5SSøren Andersen #include <linux/ioctl.h> 35920f91e5SSøren Andersen #include <linux/miscdevice.h> 36920f91e5SSøren Andersen #include <linux/reboot.h> 37920f91e5SSøren Andersen #include <linux/watchdog.h> 38920f91e5SSøren Andersen #endif 39bf4994d7SScott Wood 40bf4994d7SScott Wood #define DS1374_REG_TOD0 0x00 /* Time of Day */ 41bf4994d7SScott Wood #define DS1374_REG_TOD1 0x01 42bf4994d7SScott Wood #define DS1374_REG_TOD2 0x02 43bf4994d7SScott Wood #define DS1374_REG_TOD3 0x03 44bf4994d7SScott Wood #define DS1374_REG_WDALM0 0x04 /* Watchdog/Alarm */ 45bf4994d7SScott Wood #define DS1374_REG_WDALM1 0x05 46bf4994d7SScott Wood #define DS1374_REG_WDALM2 0x06 47bf4994d7SScott Wood #define DS1374_REG_CR 0x07 /* Control */ 48bf4994d7SScott Wood #define DS1374_REG_CR_AIE 0x01 /* Alarm Int. Enable */ 49bf4994d7SScott Wood #define DS1374_REG_CR_WDALM 0x20 /* 1=Watchdog, 0=Alarm */ 50bf4994d7SScott Wood #define DS1374_REG_CR_WACE 0x40 /* WD/Alarm counter enable */ 51bf4994d7SScott Wood #define DS1374_REG_SR 0x08 /* Status */ 52bf4994d7SScott Wood #define DS1374_REG_SR_OSF 0x80 /* Oscillator Stop Flag */ 53bf4994d7SScott Wood #define DS1374_REG_SR_AF 0x01 /* Alarm Flag */ 54bf4994d7SScott Wood #define DS1374_REG_TCR 0x09 /* Trickle Charge */ 55bf4994d7SScott Wood 563760f736SJean Delvare static const struct i2c_device_id ds1374_id[] = { 57f2eb4327SJean Delvare { "ds1374", 0 }, 583760f736SJean Delvare { } 593760f736SJean Delvare }; 603760f736SJean Delvare MODULE_DEVICE_TABLE(i2c, ds1374_id); 613760f736SJean Delvare 62920f91e5SSøren Andersen #ifdef CONFIG_OF 63920f91e5SSøren Andersen static const struct of_device_id ds1374_of_match[] = { 64920f91e5SSøren Andersen { .compatible = "dallas,ds1374" }, 65920f91e5SSøren Andersen { } 66920f91e5SSøren Andersen }; 67920f91e5SSøren Andersen MODULE_DEVICE_TABLE(of, ds1374_of_match); 68920f91e5SSøren Andersen #endif 69920f91e5SSøren Andersen 70bf4994d7SScott Wood struct ds1374 { 71bf4994d7SScott Wood struct i2c_client *client; 72bf4994d7SScott Wood struct rtc_device *rtc; 73bf4994d7SScott Wood struct work_struct work; 74bf4994d7SScott Wood 75bf4994d7SScott Wood /* The mutex protects alarm operations, and prevents a race 76bf4994d7SScott Wood * between the enable_irq() in the workqueue and the free_irq() 77bf4994d7SScott Wood * in the remove function. 78bf4994d7SScott Wood */ 79bf4994d7SScott Wood struct mutex mutex; 80bf4994d7SScott Wood int exiting; 81bf4994d7SScott Wood }; 82bf4994d7SScott Wood 83bf4994d7SScott Wood static struct i2c_driver ds1374_driver; 84bf4994d7SScott Wood 85bf4994d7SScott Wood static int ds1374_read_rtc(struct i2c_client *client, u32 *time, 86bf4994d7SScott Wood int reg, int nbytes) 87bf4994d7SScott Wood { 88bf4994d7SScott Wood u8 buf[4]; 89bf4994d7SScott Wood int ret; 90bf4994d7SScott Wood int i; 91bf4994d7SScott Wood 9201835fadSSrikant Ritolia if (WARN_ON(nbytes > 4)) 93bf4994d7SScott Wood return -EINVAL; 94bf4994d7SScott Wood 95bf4994d7SScott Wood ret = i2c_smbus_read_i2c_block_data(client, reg, nbytes, buf); 96bf4994d7SScott Wood 97bf4994d7SScott Wood if (ret < 0) 98bf4994d7SScott Wood return ret; 99bf4994d7SScott Wood if (ret < nbytes) 100bf4994d7SScott Wood return -EIO; 101bf4994d7SScott Wood 102bf4994d7SScott Wood for (i = nbytes - 1, *time = 0; i >= 0; i--) 103bf4994d7SScott Wood *time = (*time << 8) | buf[i]; 104bf4994d7SScott Wood 105bf4994d7SScott Wood return 0; 106bf4994d7SScott Wood } 107bf4994d7SScott Wood 108bf4994d7SScott Wood static int ds1374_write_rtc(struct i2c_client *client, u32 time, 109bf4994d7SScott Wood int reg, int nbytes) 110bf4994d7SScott Wood { 111bf4994d7SScott Wood u8 buf[4]; 112bf4994d7SScott Wood int i; 113bf4994d7SScott Wood 114bf4994d7SScott Wood if (nbytes > 4) { 115bf4994d7SScott Wood WARN_ON(1); 116bf4994d7SScott Wood return -EINVAL; 117bf4994d7SScott Wood } 118bf4994d7SScott Wood 119bf4994d7SScott Wood for (i = 0; i < nbytes; i++) { 120bf4994d7SScott Wood buf[i] = time & 0xff; 121bf4994d7SScott Wood time >>= 8; 122bf4994d7SScott Wood } 123bf4994d7SScott Wood 124bf4994d7SScott Wood return i2c_smbus_write_i2c_block_data(client, reg, nbytes, buf); 125bf4994d7SScott Wood } 126bf4994d7SScott Wood 127bf4994d7SScott Wood static int ds1374_check_rtc_status(struct i2c_client *client) 128bf4994d7SScott Wood { 129bf4994d7SScott Wood int ret = 0; 130bf4994d7SScott Wood int control, stat; 131bf4994d7SScott Wood 132bf4994d7SScott Wood stat = i2c_smbus_read_byte_data(client, DS1374_REG_SR); 133bf4994d7SScott Wood if (stat < 0) 134bf4994d7SScott Wood return stat; 135bf4994d7SScott Wood 136bf4994d7SScott Wood if (stat & DS1374_REG_SR_OSF) 137bf4994d7SScott Wood dev_warn(&client->dev, 138adc7b9b6SSachin Kamat "oscillator discontinuity flagged, time unreliable\n"); 139bf4994d7SScott Wood 140bf4994d7SScott Wood stat &= ~(DS1374_REG_SR_OSF | DS1374_REG_SR_AF); 141bf4994d7SScott Wood 142bf4994d7SScott Wood ret = i2c_smbus_write_byte_data(client, DS1374_REG_SR, stat); 143bf4994d7SScott Wood if (ret < 0) 144bf4994d7SScott Wood return ret; 145bf4994d7SScott Wood 146bf4994d7SScott Wood /* If the alarm is pending, clear it before requesting 147bf4994d7SScott Wood * the interrupt, so an interrupt event isn't reported 148bf4994d7SScott Wood * before everything is initialized. 149bf4994d7SScott Wood */ 150bf4994d7SScott Wood 151bf4994d7SScott Wood control = i2c_smbus_read_byte_data(client, DS1374_REG_CR); 152bf4994d7SScott Wood if (control < 0) 153bf4994d7SScott Wood return control; 154bf4994d7SScott Wood 155bf4994d7SScott Wood control &= ~(DS1374_REG_CR_WACE | DS1374_REG_CR_AIE); 156bf4994d7SScott Wood return i2c_smbus_write_byte_data(client, DS1374_REG_CR, control); 157bf4994d7SScott Wood } 158bf4994d7SScott Wood 159bf4994d7SScott Wood static int ds1374_read_time(struct device *dev, struct rtc_time *time) 160bf4994d7SScott Wood { 161bf4994d7SScott Wood struct i2c_client *client = to_i2c_client(dev); 162bf4994d7SScott Wood u32 itime; 163bf4994d7SScott Wood int ret; 164bf4994d7SScott Wood 165bf4994d7SScott Wood ret = ds1374_read_rtc(client, &itime, DS1374_REG_TOD0, 4); 166bf4994d7SScott Wood if (!ret) 167bf4994d7SScott Wood rtc_time_to_tm(itime, time); 168bf4994d7SScott Wood 169bf4994d7SScott Wood return ret; 170bf4994d7SScott Wood } 171bf4994d7SScott Wood 172bf4994d7SScott Wood static int ds1374_set_time(struct device *dev, struct rtc_time *time) 173bf4994d7SScott Wood { 174bf4994d7SScott Wood struct i2c_client *client = to_i2c_client(dev); 175bf4994d7SScott Wood unsigned long itime; 176bf4994d7SScott Wood 177bf4994d7SScott Wood rtc_tm_to_time(time, &itime); 178bf4994d7SScott Wood return ds1374_write_rtc(client, itime, DS1374_REG_TOD0, 4); 179bf4994d7SScott Wood } 180bf4994d7SScott Wood 181920f91e5SSøren Andersen #ifndef CONFIG_RTC_DRV_DS1374_WDT 182bf4994d7SScott Wood /* The ds1374 has a decrementer for an alarm, rather than a comparator. 183bf4994d7SScott Wood * If the time of day is changed, then the alarm will need to be 184bf4994d7SScott Wood * reset. 185bf4994d7SScott Wood */ 186bf4994d7SScott Wood static int ds1374_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) 187bf4994d7SScott Wood { 188bf4994d7SScott Wood struct i2c_client *client = to_i2c_client(dev); 189bf4994d7SScott Wood struct ds1374 *ds1374 = i2c_get_clientdata(client); 190bf4994d7SScott Wood u32 now, cur_alarm; 191bf4994d7SScott Wood int cr, sr; 192bf4994d7SScott Wood int ret = 0; 193bf4994d7SScott Wood 194b42f9317SAnton Vorontsov if (client->irq <= 0) 195bf4994d7SScott Wood return -EINVAL; 196bf4994d7SScott Wood 197bf4994d7SScott Wood mutex_lock(&ds1374->mutex); 198bf4994d7SScott Wood 199bf4994d7SScott Wood cr = ret = i2c_smbus_read_byte_data(client, DS1374_REG_CR); 200bf4994d7SScott Wood if (ret < 0) 201bf4994d7SScott Wood goto out; 202bf4994d7SScott Wood 203bf4994d7SScott Wood sr = ret = i2c_smbus_read_byte_data(client, DS1374_REG_SR); 204bf4994d7SScott Wood if (ret < 0) 205bf4994d7SScott Wood goto out; 206bf4994d7SScott Wood 207bf4994d7SScott Wood ret = ds1374_read_rtc(client, &now, DS1374_REG_TOD0, 4); 208bf4994d7SScott Wood if (ret) 209bf4994d7SScott Wood goto out; 210bf4994d7SScott Wood 211bf4994d7SScott Wood ret = ds1374_read_rtc(client, &cur_alarm, DS1374_REG_WDALM0, 3); 212bf4994d7SScott Wood if (ret) 213bf4994d7SScott Wood goto out; 214bf4994d7SScott Wood 215bf4994d7SScott Wood rtc_time_to_tm(now + cur_alarm, &alarm->time); 216bf4994d7SScott Wood alarm->enabled = !!(cr & DS1374_REG_CR_WACE); 217bf4994d7SScott Wood alarm->pending = !!(sr & DS1374_REG_SR_AF); 218bf4994d7SScott Wood 219bf4994d7SScott Wood out: 220bf4994d7SScott Wood mutex_unlock(&ds1374->mutex); 221bf4994d7SScott Wood return ret; 222bf4994d7SScott Wood } 223bf4994d7SScott Wood 224bf4994d7SScott Wood static int ds1374_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) 225bf4994d7SScott Wood { 226bf4994d7SScott Wood struct i2c_client *client = to_i2c_client(dev); 227bf4994d7SScott Wood struct ds1374 *ds1374 = i2c_get_clientdata(client); 228bf4994d7SScott Wood struct rtc_time now; 229bf4994d7SScott Wood unsigned long new_alarm, itime; 230bf4994d7SScott Wood int cr; 231bf4994d7SScott Wood int ret = 0; 232bf4994d7SScott Wood 233b42f9317SAnton Vorontsov if (client->irq <= 0) 234bf4994d7SScott Wood return -EINVAL; 235bf4994d7SScott Wood 236bf4994d7SScott Wood ret = ds1374_read_time(dev, &now); 237bf4994d7SScott Wood if (ret < 0) 238bf4994d7SScott Wood return ret; 239bf4994d7SScott Wood 240bf4994d7SScott Wood rtc_tm_to_time(&alarm->time, &new_alarm); 241bf4994d7SScott Wood rtc_tm_to_time(&now, &itime); 242bf4994d7SScott Wood 243bf4994d7SScott Wood /* This can happen due to races, in addition to dates that are 244bf4994d7SScott Wood * truly in the past. To avoid requiring the caller to check for 245bf4994d7SScott Wood * races, dates in the past are assumed to be in the recent past 246bf4994d7SScott Wood * (i.e. not something that we'd rather the caller know about via 247bf4994d7SScott Wood * an error), and the alarm is set to go off as soon as possible. 248bf4994d7SScott Wood */ 249fa7af8b1SRoel Kluin if (time_before_eq(new_alarm, itime)) 250bf4994d7SScott Wood new_alarm = 1; 251fa7af8b1SRoel Kluin else 252fa7af8b1SRoel Kluin new_alarm -= itime; 253bf4994d7SScott Wood 254bf4994d7SScott Wood mutex_lock(&ds1374->mutex); 255bf4994d7SScott Wood 256bf4994d7SScott Wood ret = cr = i2c_smbus_read_byte_data(client, DS1374_REG_CR); 257bf4994d7SScott Wood if (ret < 0) 258bf4994d7SScott Wood goto out; 259bf4994d7SScott Wood 260bf4994d7SScott Wood /* Disable any existing alarm before setting the new one 261bf4994d7SScott Wood * (or lack thereof). */ 262bf4994d7SScott Wood cr &= ~DS1374_REG_CR_WACE; 263bf4994d7SScott Wood 264bf4994d7SScott Wood ret = i2c_smbus_write_byte_data(client, DS1374_REG_CR, cr); 265bf4994d7SScott Wood if (ret < 0) 266bf4994d7SScott Wood goto out; 267bf4994d7SScott Wood 268bf4994d7SScott Wood ret = ds1374_write_rtc(client, new_alarm, DS1374_REG_WDALM0, 3); 269bf4994d7SScott Wood if (ret) 270bf4994d7SScott Wood goto out; 271bf4994d7SScott Wood 272bf4994d7SScott Wood if (alarm->enabled) { 273bf4994d7SScott Wood cr |= DS1374_REG_CR_WACE | DS1374_REG_CR_AIE; 274bf4994d7SScott Wood cr &= ~DS1374_REG_CR_WDALM; 275bf4994d7SScott Wood 276bf4994d7SScott Wood ret = i2c_smbus_write_byte_data(client, DS1374_REG_CR, cr); 277bf4994d7SScott Wood } 278bf4994d7SScott Wood 279bf4994d7SScott Wood out: 280bf4994d7SScott Wood mutex_unlock(&ds1374->mutex); 281bf4994d7SScott Wood return ret; 282bf4994d7SScott Wood } 283920f91e5SSøren Andersen #endif 284bf4994d7SScott Wood 285bf4994d7SScott Wood static irqreturn_t ds1374_irq(int irq, void *dev_id) 286bf4994d7SScott Wood { 287bf4994d7SScott Wood struct i2c_client *client = dev_id; 288bf4994d7SScott Wood struct ds1374 *ds1374 = i2c_get_clientdata(client); 289bf4994d7SScott Wood 290bf4994d7SScott Wood disable_irq_nosync(irq); 291bf4994d7SScott Wood schedule_work(&ds1374->work); 292bf4994d7SScott Wood return IRQ_HANDLED; 293bf4994d7SScott Wood } 294bf4994d7SScott Wood 295bf4994d7SScott Wood static void ds1374_work(struct work_struct *work) 296bf4994d7SScott Wood { 297bf4994d7SScott Wood struct ds1374 *ds1374 = container_of(work, struct ds1374, work); 298bf4994d7SScott Wood struct i2c_client *client = ds1374->client; 299bf4994d7SScott Wood int stat, control; 300bf4994d7SScott Wood 301bf4994d7SScott Wood mutex_lock(&ds1374->mutex); 302bf4994d7SScott Wood 303bf4994d7SScott Wood stat = i2c_smbus_read_byte_data(client, DS1374_REG_SR); 304bf4994d7SScott Wood if (stat < 0) 30528df30e6SJiri Slaby goto unlock; 306bf4994d7SScott Wood 307bf4994d7SScott Wood if (stat & DS1374_REG_SR_AF) { 308bf4994d7SScott Wood stat &= ~DS1374_REG_SR_AF; 309bf4994d7SScott Wood i2c_smbus_write_byte_data(client, DS1374_REG_SR, stat); 310bf4994d7SScott Wood 311bf4994d7SScott Wood control = i2c_smbus_read_byte_data(client, DS1374_REG_CR); 312bf4994d7SScott Wood if (control < 0) 313bf4994d7SScott Wood goto out; 314bf4994d7SScott Wood 315bf4994d7SScott Wood control &= ~(DS1374_REG_CR_WACE | DS1374_REG_CR_AIE); 316bf4994d7SScott Wood i2c_smbus_write_byte_data(client, DS1374_REG_CR, control); 317bf4994d7SScott Wood 318bf4994d7SScott Wood rtc_update_irq(ds1374->rtc, 1, RTC_AF | RTC_IRQF); 319bf4994d7SScott Wood } 320bf4994d7SScott Wood 321bf4994d7SScott Wood out: 322bf4994d7SScott Wood if (!ds1374->exiting) 323bf4994d7SScott Wood enable_irq(client->irq); 32428df30e6SJiri Slaby unlock: 325bf4994d7SScott Wood mutex_unlock(&ds1374->mutex); 326bf4994d7SScott Wood } 327bf4994d7SScott Wood 328920f91e5SSøren Andersen #ifndef CONFIG_RTC_DRV_DS1374_WDT 32916380c15SJohn Stultz static int ds1374_alarm_irq_enable(struct device *dev, unsigned int enabled) 330bf4994d7SScott Wood { 331bf4994d7SScott Wood struct i2c_client *client = to_i2c_client(dev); 332bf4994d7SScott Wood struct ds1374 *ds1374 = i2c_get_clientdata(client); 33316380c15SJohn Stultz int ret; 334bf4994d7SScott Wood 335bf4994d7SScott Wood mutex_lock(&ds1374->mutex); 336bf4994d7SScott Wood 337bf4994d7SScott Wood ret = i2c_smbus_read_byte_data(client, DS1374_REG_CR); 338bf4994d7SScott Wood if (ret < 0) 339bf4994d7SScott Wood goto out; 340bf4994d7SScott Wood 34116380c15SJohn Stultz if (enabled) { 342bf4994d7SScott Wood ret |= DS1374_REG_CR_WACE | DS1374_REG_CR_AIE; 343bf4994d7SScott Wood ret &= ~DS1374_REG_CR_WDALM; 34416380c15SJohn Stultz } else { 34516380c15SJohn Stultz ret &= ~DS1374_REG_CR_WACE; 346bf4994d7SScott Wood } 34716380c15SJohn Stultz ret = i2c_smbus_write_byte_data(client, DS1374_REG_CR, ret); 348bf4994d7SScott Wood 349bf4994d7SScott Wood out: 350bf4994d7SScott Wood mutex_unlock(&ds1374->mutex); 351bf4994d7SScott Wood return ret; 352bf4994d7SScott Wood } 353920f91e5SSøren Andersen #endif 354bf4994d7SScott Wood 355bf4994d7SScott Wood static const struct rtc_class_ops ds1374_rtc_ops = { 356bf4994d7SScott Wood .read_time = ds1374_read_time, 357bf4994d7SScott Wood .set_time = ds1374_set_time, 358920f91e5SSøren Andersen #ifndef CONFIG_RTC_DRV_DS1374_WDT 359bf4994d7SScott Wood .read_alarm = ds1374_read_alarm, 360bf4994d7SScott Wood .set_alarm = ds1374_set_alarm, 36116380c15SJohn Stultz .alarm_irq_enable = ds1374_alarm_irq_enable, 362920f91e5SSøren Andersen #endif 363bf4994d7SScott Wood }; 364bf4994d7SScott Wood 365920f91e5SSøren Andersen #ifdef CONFIG_RTC_DRV_DS1374_WDT 366920f91e5SSøren Andersen /* 367920f91e5SSøren Andersen ***************************************************************************** 368920f91e5SSøren Andersen * 369920f91e5SSøren Andersen * Watchdog Driver 370920f91e5SSøren Andersen * 371920f91e5SSøren Andersen ***************************************************************************** 372920f91e5SSøren Andersen */ 373920f91e5SSøren Andersen static struct i2c_client *save_client; 374920f91e5SSøren Andersen /* Default margin */ 375920f91e5SSøren Andersen #define WD_TIMO 131762 376920f91e5SSøren Andersen 377920f91e5SSøren Andersen #define DRV_NAME "DS1374 Watchdog" 378920f91e5SSøren Andersen 379920f91e5SSøren Andersen static int wdt_margin = WD_TIMO; 380920f91e5SSøren Andersen static unsigned long wdt_is_open; 381920f91e5SSøren Andersen module_param(wdt_margin, int, 0); 382920f91e5SSøren Andersen MODULE_PARM_DESC(wdt_margin, "Watchdog timeout in seconds (default 32s)"); 383920f91e5SSøren Andersen 384920f91e5SSøren Andersen static const struct watchdog_info ds1374_wdt_info = { 385920f91e5SSøren Andersen .identity = "DS1374 WTD", 386920f91e5SSøren Andersen .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | 387920f91e5SSøren Andersen WDIOF_MAGICCLOSE, 388920f91e5SSøren Andersen }; 389920f91e5SSøren Andersen 390920f91e5SSøren Andersen static int ds1374_wdt_settimeout(unsigned int timeout) 391920f91e5SSøren Andersen { 392920f91e5SSøren Andersen int ret = -ENOIOCTLCMD; 393920f91e5SSøren Andersen int cr; 394920f91e5SSøren Andersen 395920f91e5SSøren Andersen ret = cr = i2c_smbus_read_byte_data(save_client, DS1374_REG_CR); 396920f91e5SSøren Andersen if (ret < 0) 397920f91e5SSøren Andersen goto out; 398920f91e5SSøren Andersen 399920f91e5SSøren Andersen /* Disable any existing watchdog/alarm before setting the new one */ 400920f91e5SSøren Andersen cr &= ~DS1374_REG_CR_WACE; 401920f91e5SSøren Andersen 402920f91e5SSøren Andersen ret = i2c_smbus_write_byte_data(save_client, DS1374_REG_CR, cr); 403920f91e5SSøren Andersen if (ret < 0) 404920f91e5SSøren Andersen goto out; 405920f91e5SSøren Andersen 406920f91e5SSøren Andersen /* Set new watchdog time */ 407920f91e5SSøren Andersen ret = ds1374_write_rtc(save_client, timeout, DS1374_REG_WDALM0, 3); 408920f91e5SSøren Andersen if (ret) { 409a737e835SJoe Perches pr_info("couldn't set new watchdog time\n"); 410920f91e5SSøren Andersen goto out; 411920f91e5SSøren Andersen } 412920f91e5SSøren Andersen 413920f91e5SSøren Andersen /* Enable watchdog timer */ 414920f91e5SSøren Andersen cr |= DS1374_REG_CR_WACE | DS1374_REG_CR_WDALM; 415920f91e5SSøren Andersen cr &= ~DS1374_REG_CR_AIE; 416920f91e5SSøren Andersen 417920f91e5SSøren Andersen ret = i2c_smbus_write_byte_data(save_client, DS1374_REG_CR, cr); 418920f91e5SSøren Andersen if (ret < 0) 419920f91e5SSøren Andersen goto out; 420920f91e5SSøren Andersen 421920f91e5SSøren Andersen return 0; 422920f91e5SSøren Andersen out: 423920f91e5SSøren Andersen return ret; 424920f91e5SSøren Andersen } 425920f91e5SSøren Andersen 426920f91e5SSøren Andersen 427920f91e5SSøren Andersen /* 428920f91e5SSøren Andersen * Reload the watchdog timer. (ie, pat the watchdog) 429920f91e5SSøren Andersen */ 430920f91e5SSøren Andersen static void ds1374_wdt_ping(void) 431920f91e5SSøren Andersen { 432920f91e5SSøren Andersen u32 val; 433920f91e5SSøren Andersen int ret = 0; 434920f91e5SSøren Andersen 435920f91e5SSøren Andersen ret = ds1374_read_rtc(save_client, &val, DS1374_REG_WDALM0, 3); 436920f91e5SSøren Andersen if (ret) 437920f91e5SSøren Andersen pr_info("WD TICK FAIL!!!!!!!!!! %i\n", ret); 438920f91e5SSøren Andersen } 439920f91e5SSøren Andersen 440920f91e5SSøren Andersen static void ds1374_wdt_disable(void) 441920f91e5SSøren Andersen { 442920f91e5SSøren Andersen int ret = -ENOIOCTLCMD; 443920f91e5SSøren Andersen int cr; 444920f91e5SSøren Andersen 445920f91e5SSøren Andersen cr = i2c_smbus_read_byte_data(save_client, DS1374_REG_CR); 446920f91e5SSøren Andersen /* Disable watchdog timer */ 447920f91e5SSøren Andersen cr &= ~DS1374_REG_CR_WACE; 448920f91e5SSøren Andersen 449920f91e5SSøren Andersen ret = i2c_smbus_write_byte_data(save_client, DS1374_REG_CR, cr); 450920f91e5SSøren Andersen } 451920f91e5SSøren Andersen 452920f91e5SSøren Andersen /* 453920f91e5SSøren Andersen * Watchdog device is opened, and watchdog starts running. 454920f91e5SSøren Andersen */ 455920f91e5SSøren Andersen static int ds1374_wdt_open(struct inode *inode, struct file *file) 456920f91e5SSøren Andersen { 457920f91e5SSøren Andersen struct ds1374 *ds1374 = i2c_get_clientdata(save_client); 458920f91e5SSøren Andersen 459920f91e5SSøren Andersen if (MINOR(inode->i_rdev) == WATCHDOG_MINOR) { 460920f91e5SSøren Andersen mutex_lock(&ds1374->mutex); 461920f91e5SSøren Andersen if (test_and_set_bit(0, &wdt_is_open)) { 462920f91e5SSøren Andersen mutex_unlock(&ds1374->mutex); 463920f91e5SSøren Andersen return -EBUSY; 464920f91e5SSøren Andersen } 465920f91e5SSøren Andersen /* 466920f91e5SSøren Andersen * Activate 467920f91e5SSøren Andersen */ 468920f91e5SSøren Andersen wdt_is_open = 1; 469920f91e5SSøren Andersen mutex_unlock(&ds1374->mutex); 470920f91e5SSøren Andersen return nonseekable_open(inode, file); 471920f91e5SSøren Andersen } 472920f91e5SSøren Andersen return -ENODEV; 473920f91e5SSøren Andersen } 474920f91e5SSøren Andersen 475920f91e5SSøren Andersen /* 476920f91e5SSøren Andersen * Close the watchdog device. 477920f91e5SSøren Andersen */ 478920f91e5SSøren Andersen static int ds1374_wdt_release(struct inode *inode, struct file *file) 479920f91e5SSøren Andersen { 480920f91e5SSøren Andersen if (MINOR(inode->i_rdev) == WATCHDOG_MINOR) 481920f91e5SSøren Andersen clear_bit(0, &wdt_is_open); 482920f91e5SSøren Andersen 483920f91e5SSøren Andersen return 0; 484920f91e5SSøren Andersen } 485920f91e5SSøren Andersen 486920f91e5SSøren Andersen /* 487920f91e5SSøren Andersen * Pat the watchdog whenever device is written to. 488920f91e5SSøren Andersen */ 489920f91e5SSøren Andersen static ssize_t ds1374_wdt_write(struct file *file, const char __user *data, 490920f91e5SSøren Andersen size_t len, loff_t *ppos) 491920f91e5SSøren Andersen { 492920f91e5SSøren Andersen if (len) { 493920f91e5SSøren Andersen ds1374_wdt_ping(); 494920f91e5SSøren Andersen return 1; 495920f91e5SSøren Andersen } 496920f91e5SSøren Andersen return 0; 497920f91e5SSøren Andersen } 498920f91e5SSøren Andersen 499920f91e5SSøren Andersen static ssize_t ds1374_wdt_read(struct file *file, char __user *data, 500920f91e5SSøren Andersen size_t len, loff_t *ppos) 501920f91e5SSøren Andersen { 502920f91e5SSøren Andersen return 0; 503920f91e5SSøren Andersen } 504920f91e5SSøren Andersen 505920f91e5SSøren Andersen /* 506920f91e5SSøren Andersen * Handle commands from user-space. 507920f91e5SSøren Andersen */ 508920f91e5SSøren Andersen static long ds1374_wdt_ioctl(struct file *file, unsigned int cmd, 509920f91e5SSøren Andersen unsigned long arg) 510920f91e5SSøren Andersen { 511920f91e5SSøren Andersen int new_margin, options; 512920f91e5SSøren Andersen 513920f91e5SSøren Andersen switch (cmd) { 514920f91e5SSøren Andersen case WDIOC_GETSUPPORT: 515920f91e5SSøren Andersen return copy_to_user((struct watchdog_info __user *)arg, 516920f91e5SSøren Andersen &ds1374_wdt_info, sizeof(ds1374_wdt_info)) ? -EFAULT : 0; 517920f91e5SSøren Andersen 518920f91e5SSøren Andersen case WDIOC_GETSTATUS: 519920f91e5SSøren Andersen case WDIOC_GETBOOTSTATUS: 520920f91e5SSøren Andersen return put_user(0, (int __user *)arg); 521920f91e5SSøren Andersen case WDIOC_KEEPALIVE: 522920f91e5SSøren Andersen ds1374_wdt_ping(); 523920f91e5SSøren Andersen return 0; 524920f91e5SSøren Andersen case WDIOC_SETTIMEOUT: 525920f91e5SSøren Andersen if (get_user(new_margin, (int __user *)arg)) 526920f91e5SSøren Andersen return -EFAULT; 527920f91e5SSøren Andersen 528920f91e5SSøren Andersen if (new_margin < 1 || new_margin > 16777216) 529920f91e5SSøren Andersen return -EINVAL; 530920f91e5SSøren Andersen 531920f91e5SSøren Andersen wdt_margin = new_margin; 532920f91e5SSøren Andersen ds1374_wdt_settimeout(new_margin); 533920f91e5SSøren Andersen ds1374_wdt_ping(); 534920f91e5SSøren Andersen /* fallthrough */ 535920f91e5SSøren Andersen case WDIOC_GETTIMEOUT: 536920f91e5SSøren Andersen return put_user(wdt_margin, (int __user *)arg); 537920f91e5SSøren Andersen case WDIOC_SETOPTIONS: 538920f91e5SSøren Andersen if (copy_from_user(&options, (int __user *)arg, sizeof(int))) 539920f91e5SSøren Andersen return -EFAULT; 540920f91e5SSøren Andersen 541920f91e5SSøren Andersen if (options & WDIOS_DISABLECARD) { 542a737e835SJoe Perches pr_info("disable watchdog\n"); 543920f91e5SSøren Andersen ds1374_wdt_disable(); 544920f91e5SSøren Andersen } 545920f91e5SSøren Andersen 546920f91e5SSøren Andersen if (options & WDIOS_ENABLECARD) { 547a737e835SJoe Perches pr_info("enable watchdog\n"); 548920f91e5SSøren Andersen ds1374_wdt_settimeout(wdt_margin); 549920f91e5SSøren Andersen ds1374_wdt_ping(); 550920f91e5SSøren Andersen } 551920f91e5SSøren Andersen 552920f91e5SSøren Andersen return -EINVAL; 553920f91e5SSøren Andersen } 554920f91e5SSøren Andersen return -ENOTTY; 555920f91e5SSøren Andersen } 556920f91e5SSøren Andersen 557920f91e5SSøren Andersen static long ds1374_wdt_unlocked_ioctl(struct file *file, unsigned int cmd, 558920f91e5SSøren Andersen unsigned long arg) 559920f91e5SSøren Andersen { 560920f91e5SSøren Andersen int ret; 561920f91e5SSøren Andersen struct ds1374 *ds1374 = i2c_get_clientdata(save_client); 562920f91e5SSøren Andersen 563920f91e5SSøren Andersen mutex_lock(&ds1374->mutex); 564920f91e5SSøren Andersen ret = ds1374_wdt_ioctl(file, cmd, arg); 565920f91e5SSøren Andersen mutex_unlock(&ds1374->mutex); 566920f91e5SSøren Andersen 567920f91e5SSøren Andersen return ret; 568920f91e5SSøren Andersen } 569920f91e5SSøren Andersen 570920f91e5SSøren Andersen static int ds1374_wdt_notify_sys(struct notifier_block *this, 571920f91e5SSøren Andersen unsigned long code, void *unused) 572920f91e5SSøren Andersen { 573920f91e5SSøren Andersen if (code == SYS_DOWN || code == SYS_HALT) 574920f91e5SSøren Andersen /* Disable Watchdog */ 575920f91e5SSøren Andersen ds1374_wdt_disable(); 576920f91e5SSøren Andersen return NOTIFY_DONE; 577920f91e5SSøren Andersen } 578920f91e5SSøren Andersen 579920f91e5SSøren Andersen static const struct file_operations ds1374_wdt_fops = { 580920f91e5SSøren Andersen .owner = THIS_MODULE, 581920f91e5SSøren Andersen .read = ds1374_wdt_read, 582920f91e5SSøren Andersen .unlocked_ioctl = ds1374_wdt_unlocked_ioctl, 583920f91e5SSøren Andersen .write = ds1374_wdt_write, 584920f91e5SSøren Andersen .open = ds1374_wdt_open, 585920f91e5SSøren Andersen .release = ds1374_wdt_release, 586920f91e5SSøren Andersen .llseek = no_llseek, 587920f91e5SSøren Andersen }; 588920f91e5SSøren Andersen 589920f91e5SSøren Andersen static struct miscdevice ds1374_miscdev = { 590920f91e5SSøren Andersen .minor = WATCHDOG_MINOR, 591920f91e5SSøren Andersen .name = "watchdog", 592920f91e5SSøren Andersen .fops = &ds1374_wdt_fops, 593920f91e5SSøren Andersen }; 594920f91e5SSøren Andersen 595920f91e5SSøren Andersen static struct notifier_block ds1374_wdt_notifier = { 596920f91e5SSøren Andersen .notifier_call = ds1374_wdt_notify_sys, 597920f91e5SSøren Andersen }; 598920f91e5SSøren Andersen 599920f91e5SSøren Andersen #endif /*CONFIG_RTC_DRV_DS1374_WDT*/ 600920f91e5SSøren Andersen /* 601920f91e5SSøren Andersen ***************************************************************************** 602920f91e5SSøren Andersen * 603920f91e5SSøren Andersen * Driver Interface 604920f91e5SSøren Andersen * 605920f91e5SSøren Andersen ***************************************************************************** 606920f91e5SSøren Andersen */ 607d2653e92SJean Delvare static int ds1374_probe(struct i2c_client *client, 608d2653e92SJean Delvare const struct i2c_device_id *id) 609bf4994d7SScott Wood { 610bf4994d7SScott Wood struct ds1374 *ds1374; 611bf4994d7SScott Wood int ret; 612bf4994d7SScott Wood 613d1a96639SSachin Kamat ds1374 = devm_kzalloc(&client->dev, sizeof(struct ds1374), GFP_KERNEL); 614bf4994d7SScott Wood if (!ds1374) 615bf4994d7SScott Wood return -ENOMEM; 616bf4994d7SScott Wood 617bf4994d7SScott Wood ds1374->client = client; 618bf4994d7SScott Wood i2c_set_clientdata(client, ds1374); 619bf4994d7SScott Wood 620bf4994d7SScott Wood INIT_WORK(&ds1374->work, ds1374_work); 621bf4994d7SScott Wood mutex_init(&ds1374->mutex); 622bf4994d7SScott Wood 623bf4994d7SScott Wood ret = ds1374_check_rtc_status(client); 624bf4994d7SScott Wood if (ret) 625d1a96639SSachin Kamat return ret; 626bf4994d7SScott Wood 627b42f9317SAnton Vorontsov if (client->irq > 0) { 628d1a96639SSachin Kamat ret = devm_request_irq(&client->dev, client->irq, ds1374_irq, 0, 629bf4994d7SScott Wood "ds1374", client); 630bf4994d7SScott Wood if (ret) { 631bf4994d7SScott Wood dev_err(&client->dev, "unable to request IRQ\n"); 632d1a96639SSachin Kamat return ret; 633bf4994d7SScott Wood } 63426b3c01fSAnton Vorontsov 63526b3c01fSAnton Vorontsov device_set_wakeup_capable(&client->dev, 1); 636bf4994d7SScott Wood } 637bf4994d7SScott Wood 638d1a96639SSachin Kamat ds1374->rtc = devm_rtc_device_register(&client->dev, client->name, 639bf4994d7SScott Wood &ds1374_rtc_ops, THIS_MODULE); 640bf4994d7SScott Wood if (IS_ERR(ds1374->rtc)) { 641bf4994d7SScott Wood dev_err(&client->dev, "unable to register the class device\n"); 642d1a96639SSachin Kamat return PTR_ERR(ds1374->rtc); 643bf4994d7SScott Wood } 644bf4994d7SScott Wood 645920f91e5SSøren Andersen #ifdef CONFIG_RTC_DRV_DS1374_WDT 646920f91e5SSøren Andersen save_client = client; 647920f91e5SSøren Andersen ret = misc_register(&ds1374_miscdev); 648920f91e5SSøren Andersen if (ret) 649920f91e5SSøren Andersen return ret; 650920f91e5SSøren Andersen ret = register_reboot_notifier(&ds1374_wdt_notifier); 651920f91e5SSøren Andersen if (ret) { 652920f91e5SSøren Andersen misc_deregister(&ds1374_miscdev); 653920f91e5SSøren Andersen return ret; 654920f91e5SSøren Andersen } 655920f91e5SSøren Andersen ds1374_wdt_settimeout(131072); 656920f91e5SSøren Andersen #endif 657920f91e5SSøren Andersen 658bf4994d7SScott Wood return 0; 659bf4994d7SScott Wood } 660bf4994d7SScott Wood 6615a167f45SGreg Kroah-Hartman static int ds1374_remove(struct i2c_client *client) 662bf4994d7SScott Wood { 663bf4994d7SScott Wood struct ds1374 *ds1374 = i2c_get_clientdata(client); 664920f91e5SSøren Andersen #ifdef CONFIG_RTC_DRV_DS1374_WDT 665f368ed60SGreg Kroah-Hartman misc_deregister(&ds1374_miscdev); 666920f91e5SSøren Andersen ds1374_miscdev.parent = NULL; 667920f91e5SSøren Andersen unregister_reboot_notifier(&ds1374_wdt_notifier); 668920f91e5SSøren Andersen #endif 669bf4994d7SScott Wood 670b42f9317SAnton Vorontsov if (client->irq > 0) { 671bf4994d7SScott Wood mutex_lock(&ds1374->mutex); 672bf4994d7SScott Wood ds1374->exiting = 1; 673bf4994d7SScott Wood mutex_unlock(&ds1374->mutex); 674bf4994d7SScott Wood 675d1a96639SSachin Kamat devm_free_irq(&client->dev, client->irq, client); 6769db8995bSTejun Heo cancel_work_sync(&ds1374->work); 677bf4994d7SScott Wood } 678bf4994d7SScott Wood 679bf4994d7SScott Wood return 0; 680bf4994d7SScott Wood } 681bf4994d7SScott Wood 6828b80ef64SJingoo Han #ifdef CONFIG_PM_SLEEP 683bc96ba74SMark Brown static int ds1374_suspend(struct device *dev) 684986e36a5SMarc Pignat { 685bc96ba74SMark Brown struct i2c_client *client = to_i2c_client(dev); 686bc96ba74SMark Brown 6870d9030a2SOctavian Purdila if (client->irq > 0 && device_may_wakeup(&client->dev)) 688986e36a5SMarc Pignat enable_irq_wake(client->irq); 689986e36a5SMarc Pignat return 0; 690986e36a5SMarc Pignat } 691986e36a5SMarc Pignat 692bc96ba74SMark Brown static int ds1374_resume(struct device *dev) 693986e36a5SMarc Pignat { 694bc96ba74SMark Brown struct i2c_client *client = to_i2c_client(dev); 695bc96ba74SMark Brown 6960d9030a2SOctavian Purdila if (client->irq > 0 && device_may_wakeup(&client->dev)) 697986e36a5SMarc Pignat disable_irq_wake(client->irq); 698986e36a5SMarc Pignat return 0; 699986e36a5SMarc Pignat } 7008b80ef64SJingoo Han #endif 701bc96ba74SMark Brown 702bc96ba74SMark Brown static SIMPLE_DEV_PM_OPS(ds1374_pm, ds1374_suspend, ds1374_resume); 703bc96ba74SMark Brown 704bf4994d7SScott Wood static struct i2c_driver ds1374_driver = { 705bf4994d7SScott Wood .driver = { 706bf4994d7SScott Wood .name = "rtc-ds1374", 707*abac12e1SJavier Martinez Canillas .of_match_table = of_match_ptr(ds1374_of_match), 7088b80ef64SJingoo Han .pm = &ds1374_pm, 709bf4994d7SScott Wood }, 710bf4994d7SScott Wood .probe = ds1374_probe, 7115a167f45SGreg Kroah-Hartman .remove = ds1374_remove, 7123760f736SJean Delvare .id_table = ds1374_id, 713bf4994d7SScott Wood }; 714bf4994d7SScott Wood 7150abc9201SAxel Lin module_i2c_driver(ds1374_driver); 716bf4994d7SScott Wood 717bf4994d7SScott Wood MODULE_AUTHOR("Scott Wood <scottwood@freescale.com>"); 718bf4994d7SScott Wood MODULE_DESCRIPTION("Maxim/Dallas DS1374 RTC Driver"); 719bf4994d7SScott Wood MODULE_LICENSE("GPL"); 720