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 17ccf988b6SMauro Carvalho Chehab * recommended in .../Documentation/i2c/writing-clients.rst 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 */ 49d3de4bebSJohnson CH Chen (陳昭勳) #define DS1374_REG_CR_WDSTR 0x08 /* 1=INT, 0=RST */ 50bf4994d7SScott Wood #define DS1374_REG_CR_WDALM 0x20 /* 1=Watchdog, 0=Alarm */ 51bf4994d7SScott Wood #define DS1374_REG_CR_WACE 0x40 /* WD/Alarm counter enable */ 52bf4994d7SScott Wood #define DS1374_REG_SR 0x08 /* Status */ 53bf4994d7SScott Wood #define DS1374_REG_SR_OSF 0x80 /* Oscillator Stop Flag */ 54bf4994d7SScott Wood #define DS1374_REG_SR_AF 0x01 /* Alarm Flag */ 55bf4994d7SScott Wood #define DS1374_REG_TCR 0x09 /* Trickle Charge */ 56bf4994d7SScott Wood 573760f736SJean Delvare static const struct i2c_device_id ds1374_id[] = { 58f2eb4327SJean Delvare { "ds1374", 0 }, 593760f736SJean Delvare { } 603760f736SJean Delvare }; 613760f736SJean Delvare MODULE_DEVICE_TABLE(i2c, ds1374_id); 623760f736SJean Delvare 63920f91e5SSøren Andersen #ifdef CONFIG_OF 64920f91e5SSøren Andersen static const struct of_device_id ds1374_of_match[] = { 65920f91e5SSøren Andersen { .compatible = "dallas,ds1374" }, 66920f91e5SSøren Andersen { } 67920f91e5SSøren Andersen }; 68920f91e5SSøren Andersen MODULE_DEVICE_TABLE(of, ds1374_of_match); 69920f91e5SSøren Andersen #endif 70920f91e5SSøren Andersen 71bf4994d7SScott Wood struct ds1374 { 72bf4994d7SScott Wood struct i2c_client *client; 73bf4994d7SScott Wood struct rtc_device *rtc; 74bf4994d7SScott Wood struct work_struct work; 75d3de4bebSJohnson CH Chen (陳昭勳) #ifdef CONFIG_RTC_DRV_DS1374_WDT 76d3de4bebSJohnson CH Chen (陳昭勳) struct watchdog_device wdt; 77d3de4bebSJohnson CH Chen (陳昭勳) #endif 78bf4994d7SScott Wood /* The mutex protects alarm operations, and prevents a race 79bf4994d7SScott Wood * between the enable_irq() in the workqueue and the free_irq() 80bf4994d7SScott Wood * in the remove function. 81bf4994d7SScott Wood */ 82bf4994d7SScott Wood struct mutex mutex; 83bf4994d7SScott Wood int exiting; 84bf4994d7SScott Wood }; 85bf4994d7SScott Wood 86bf4994d7SScott Wood static struct i2c_driver ds1374_driver; 87bf4994d7SScott Wood 88bf4994d7SScott Wood static int ds1374_read_rtc(struct i2c_client *client, u32 *time, 89bf4994d7SScott Wood int reg, int nbytes) 90bf4994d7SScott Wood { 91bf4994d7SScott Wood u8 buf[4]; 92bf4994d7SScott Wood int ret; 93bf4994d7SScott Wood int i; 94bf4994d7SScott Wood 9501835fadSSrikant Ritolia if (WARN_ON(nbytes > 4)) 96bf4994d7SScott Wood return -EINVAL; 97bf4994d7SScott Wood 98bf4994d7SScott Wood ret = i2c_smbus_read_i2c_block_data(client, reg, nbytes, buf); 99bf4994d7SScott Wood 100bf4994d7SScott Wood if (ret < 0) 101bf4994d7SScott Wood return ret; 102bf4994d7SScott Wood if (ret < nbytes) 103bf4994d7SScott Wood return -EIO; 104bf4994d7SScott Wood 105bf4994d7SScott Wood for (i = nbytes - 1, *time = 0; i >= 0; i--) 106bf4994d7SScott Wood *time = (*time << 8) | buf[i]; 107bf4994d7SScott Wood 108bf4994d7SScott Wood return 0; 109bf4994d7SScott Wood } 110bf4994d7SScott Wood 111bf4994d7SScott Wood static int ds1374_write_rtc(struct i2c_client *client, u32 time, 112bf4994d7SScott Wood int reg, int nbytes) 113bf4994d7SScott Wood { 114bf4994d7SScott Wood u8 buf[4]; 115bf4994d7SScott Wood int i; 116bf4994d7SScott Wood 117bf4994d7SScott Wood if (nbytes > 4) { 118bf4994d7SScott Wood WARN_ON(1); 119bf4994d7SScott Wood return -EINVAL; 120bf4994d7SScott Wood } 121bf4994d7SScott Wood 122bf4994d7SScott Wood for (i = 0; i < nbytes; i++) { 123bf4994d7SScott Wood buf[i] = time & 0xff; 124bf4994d7SScott Wood time >>= 8; 125bf4994d7SScott Wood } 126bf4994d7SScott Wood 127bf4994d7SScott Wood return i2c_smbus_write_i2c_block_data(client, reg, nbytes, buf); 128bf4994d7SScott Wood } 129bf4994d7SScott Wood 130bf4994d7SScott Wood static int ds1374_check_rtc_status(struct i2c_client *client) 131bf4994d7SScott Wood { 132bf4994d7SScott Wood int ret = 0; 133bf4994d7SScott Wood int control, stat; 134bf4994d7SScott Wood 135bf4994d7SScott Wood stat = i2c_smbus_read_byte_data(client, DS1374_REG_SR); 136bf4994d7SScott Wood if (stat < 0) 137bf4994d7SScott Wood return stat; 138bf4994d7SScott Wood 139bf4994d7SScott Wood if (stat & DS1374_REG_SR_OSF) 140bf4994d7SScott Wood dev_warn(&client->dev, 141adc7b9b6SSachin Kamat "oscillator discontinuity flagged, time unreliable\n"); 142bf4994d7SScott Wood 143bf4994d7SScott Wood stat &= ~(DS1374_REG_SR_OSF | DS1374_REG_SR_AF); 144bf4994d7SScott Wood 145bf4994d7SScott Wood ret = i2c_smbus_write_byte_data(client, DS1374_REG_SR, stat); 146bf4994d7SScott Wood if (ret < 0) 147bf4994d7SScott Wood return ret; 148bf4994d7SScott Wood 149bf4994d7SScott Wood /* If the alarm is pending, clear it before requesting 150bf4994d7SScott Wood * the interrupt, so an interrupt event isn't reported 151bf4994d7SScott Wood * before everything is initialized. 152bf4994d7SScott Wood */ 153bf4994d7SScott Wood 154bf4994d7SScott Wood control = i2c_smbus_read_byte_data(client, DS1374_REG_CR); 155bf4994d7SScott Wood if (control < 0) 156bf4994d7SScott Wood return control; 157bf4994d7SScott Wood 158bf4994d7SScott Wood control &= ~(DS1374_REG_CR_WACE | DS1374_REG_CR_AIE); 159bf4994d7SScott Wood return i2c_smbus_write_byte_data(client, DS1374_REG_CR, control); 160bf4994d7SScott Wood } 161bf4994d7SScott Wood 162bf4994d7SScott Wood static int ds1374_read_time(struct device *dev, struct rtc_time *time) 163bf4994d7SScott Wood { 164bf4994d7SScott Wood struct i2c_client *client = to_i2c_client(dev); 165bf4994d7SScott Wood u32 itime; 166bf4994d7SScott Wood int ret; 167bf4994d7SScott Wood 168bf4994d7SScott Wood ret = ds1374_read_rtc(client, &itime, DS1374_REG_TOD0, 4); 169bf4994d7SScott Wood if (!ret) 170ca824be9SAlexandre Belloni rtc_time64_to_tm(itime, time); 171bf4994d7SScott Wood 172bf4994d7SScott Wood return ret; 173bf4994d7SScott Wood } 174bf4994d7SScott Wood 175bf4994d7SScott Wood static int ds1374_set_time(struct device *dev, struct rtc_time *time) 176bf4994d7SScott Wood { 177bf4994d7SScott Wood struct i2c_client *client = to_i2c_client(dev); 178ca824be9SAlexandre Belloni unsigned long itime = rtc_tm_to_time64(time); 179bf4994d7SScott Wood 180bf4994d7SScott Wood return ds1374_write_rtc(client, itime, DS1374_REG_TOD0, 4); 181bf4994d7SScott Wood } 182bf4994d7SScott Wood 183920f91e5SSøren Andersen #ifndef CONFIG_RTC_DRV_DS1374_WDT 184bf4994d7SScott Wood /* The ds1374 has a decrementer for an alarm, rather than a comparator. 185bf4994d7SScott Wood * If the time of day is changed, then the alarm will need to be 186bf4994d7SScott Wood * reset. 187bf4994d7SScott Wood */ 188bf4994d7SScott Wood static int ds1374_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) 189bf4994d7SScott Wood { 190bf4994d7SScott Wood struct i2c_client *client = to_i2c_client(dev); 191bf4994d7SScott Wood struct ds1374 *ds1374 = i2c_get_clientdata(client); 192bf4994d7SScott Wood u32 now, cur_alarm; 193bf4994d7SScott Wood int cr, sr; 194bf4994d7SScott Wood int ret = 0; 195bf4994d7SScott Wood 196b42f9317SAnton Vorontsov if (client->irq <= 0) 197bf4994d7SScott Wood return -EINVAL; 198bf4994d7SScott Wood 199bf4994d7SScott Wood mutex_lock(&ds1374->mutex); 200bf4994d7SScott Wood 201bf4994d7SScott Wood cr = ret = i2c_smbus_read_byte_data(client, DS1374_REG_CR); 202bf4994d7SScott Wood if (ret < 0) 203bf4994d7SScott Wood goto out; 204bf4994d7SScott Wood 205bf4994d7SScott Wood sr = ret = i2c_smbus_read_byte_data(client, DS1374_REG_SR); 206bf4994d7SScott Wood if (ret < 0) 207bf4994d7SScott Wood goto out; 208bf4994d7SScott Wood 209bf4994d7SScott Wood ret = ds1374_read_rtc(client, &now, DS1374_REG_TOD0, 4); 210bf4994d7SScott Wood if (ret) 211bf4994d7SScott Wood goto out; 212bf4994d7SScott Wood 213bf4994d7SScott Wood ret = ds1374_read_rtc(client, &cur_alarm, DS1374_REG_WDALM0, 3); 214bf4994d7SScott Wood if (ret) 215bf4994d7SScott Wood goto out; 216bf4994d7SScott Wood 217ca824be9SAlexandre Belloni rtc_time64_to_tm(now + cur_alarm, &alarm->time); 218bf4994d7SScott Wood alarm->enabled = !!(cr & DS1374_REG_CR_WACE); 219bf4994d7SScott Wood alarm->pending = !!(sr & DS1374_REG_SR_AF); 220bf4994d7SScott Wood 221bf4994d7SScott Wood out: 222bf4994d7SScott Wood mutex_unlock(&ds1374->mutex); 223bf4994d7SScott Wood return ret; 224bf4994d7SScott Wood } 225bf4994d7SScott Wood 226bf4994d7SScott Wood static int ds1374_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) 227bf4994d7SScott Wood { 228bf4994d7SScott Wood struct i2c_client *client = to_i2c_client(dev); 229bf4994d7SScott Wood struct ds1374 *ds1374 = i2c_get_clientdata(client); 230bf4994d7SScott Wood struct rtc_time now; 231bf4994d7SScott Wood unsigned long new_alarm, itime; 232bf4994d7SScott Wood int cr; 233bf4994d7SScott Wood int ret = 0; 234bf4994d7SScott Wood 235b42f9317SAnton Vorontsov if (client->irq <= 0) 236bf4994d7SScott Wood return -EINVAL; 237bf4994d7SScott Wood 238bf4994d7SScott Wood ret = ds1374_read_time(dev, &now); 239bf4994d7SScott Wood if (ret < 0) 240bf4994d7SScott Wood return ret; 241bf4994d7SScott Wood 242ca824be9SAlexandre Belloni new_alarm = rtc_tm_to_time64(&alarm->time); 243ca824be9SAlexandre Belloni itime = rtc_tm_to_time64(&now); 244bf4994d7SScott Wood 245bf4994d7SScott Wood /* This can happen due to races, in addition to dates that are 246bf4994d7SScott Wood * truly in the past. To avoid requiring the caller to check for 247bf4994d7SScott Wood * races, dates in the past are assumed to be in the recent past 248bf4994d7SScott Wood * (i.e. not something that we'd rather the caller know about via 249bf4994d7SScott Wood * an error), and the alarm is set to go off as soon as possible. 250bf4994d7SScott Wood */ 251fa7af8b1SRoel Kluin if (time_before_eq(new_alarm, itime)) 252bf4994d7SScott Wood new_alarm = 1; 253fa7af8b1SRoel Kluin else 254fa7af8b1SRoel Kluin new_alarm -= itime; 255bf4994d7SScott Wood 256bf4994d7SScott Wood mutex_lock(&ds1374->mutex); 257bf4994d7SScott Wood 258bf4994d7SScott Wood ret = cr = i2c_smbus_read_byte_data(client, DS1374_REG_CR); 259bf4994d7SScott Wood if (ret < 0) 260bf4994d7SScott Wood goto out; 261bf4994d7SScott Wood 262bf4994d7SScott Wood /* Disable any existing alarm before setting the new one 263bf4994d7SScott Wood * (or lack thereof). */ 264bf4994d7SScott Wood cr &= ~DS1374_REG_CR_WACE; 265bf4994d7SScott Wood 266bf4994d7SScott Wood ret = i2c_smbus_write_byte_data(client, DS1374_REG_CR, cr); 267bf4994d7SScott Wood if (ret < 0) 268bf4994d7SScott Wood goto out; 269bf4994d7SScott Wood 270bf4994d7SScott Wood ret = ds1374_write_rtc(client, new_alarm, DS1374_REG_WDALM0, 3); 271bf4994d7SScott Wood if (ret) 272bf4994d7SScott Wood goto out; 273bf4994d7SScott Wood 274bf4994d7SScott Wood if (alarm->enabled) { 275bf4994d7SScott Wood cr |= DS1374_REG_CR_WACE | DS1374_REG_CR_AIE; 276bf4994d7SScott Wood cr &= ~DS1374_REG_CR_WDALM; 277bf4994d7SScott Wood 278bf4994d7SScott Wood ret = i2c_smbus_write_byte_data(client, DS1374_REG_CR, cr); 279bf4994d7SScott Wood } 280bf4994d7SScott Wood 281bf4994d7SScott Wood out: 282bf4994d7SScott Wood mutex_unlock(&ds1374->mutex); 283bf4994d7SScott Wood return ret; 284bf4994d7SScott Wood } 285920f91e5SSøren Andersen #endif 286bf4994d7SScott Wood 287bf4994d7SScott Wood static irqreturn_t ds1374_irq(int irq, void *dev_id) 288bf4994d7SScott Wood { 289bf4994d7SScott Wood struct i2c_client *client = dev_id; 290bf4994d7SScott Wood struct ds1374 *ds1374 = i2c_get_clientdata(client); 291bf4994d7SScott Wood 292bf4994d7SScott Wood disable_irq_nosync(irq); 293bf4994d7SScott Wood schedule_work(&ds1374->work); 294bf4994d7SScott Wood return IRQ_HANDLED; 295bf4994d7SScott Wood } 296bf4994d7SScott Wood 297bf4994d7SScott Wood static void ds1374_work(struct work_struct *work) 298bf4994d7SScott Wood { 299bf4994d7SScott Wood struct ds1374 *ds1374 = container_of(work, struct ds1374, work); 300bf4994d7SScott Wood struct i2c_client *client = ds1374->client; 301bf4994d7SScott Wood int stat, control; 302bf4994d7SScott Wood 303bf4994d7SScott Wood mutex_lock(&ds1374->mutex); 304bf4994d7SScott Wood 305bf4994d7SScott Wood stat = i2c_smbus_read_byte_data(client, DS1374_REG_SR); 306bf4994d7SScott Wood if (stat < 0) 30728df30e6SJiri Slaby goto unlock; 308bf4994d7SScott Wood 309bf4994d7SScott Wood if (stat & DS1374_REG_SR_AF) { 310bf4994d7SScott Wood stat &= ~DS1374_REG_SR_AF; 311bf4994d7SScott Wood i2c_smbus_write_byte_data(client, DS1374_REG_SR, stat); 312bf4994d7SScott Wood 313bf4994d7SScott Wood control = i2c_smbus_read_byte_data(client, DS1374_REG_CR); 314bf4994d7SScott Wood if (control < 0) 315bf4994d7SScott Wood goto out; 316bf4994d7SScott Wood 317bf4994d7SScott Wood control &= ~(DS1374_REG_CR_WACE | DS1374_REG_CR_AIE); 318bf4994d7SScott Wood i2c_smbus_write_byte_data(client, DS1374_REG_CR, control); 319bf4994d7SScott Wood 320bf4994d7SScott Wood rtc_update_irq(ds1374->rtc, 1, RTC_AF | RTC_IRQF); 321bf4994d7SScott Wood } 322bf4994d7SScott Wood 323bf4994d7SScott Wood out: 324bf4994d7SScott Wood if (!ds1374->exiting) 325bf4994d7SScott Wood enable_irq(client->irq); 32628df30e6SJiri Slaby unlock: 327bf4994d7SScott Wood mutex_unlock(&ds1374->mutex); 328bf4994d7SScott Wood } 329bf4994d7SScott Wood 330920f91e5SSøren Andersen #ifndef CONFIG_RTC_DRV_DS1374_WDT 33116380c15SJohn Stultz static int ds1374_alarm_irq_enable(struct device *dev, unsigned int enabled) 332bf4994d7SScott Wood { 333bf4994d7SScott Wood struct i2c_client *client = to_i2c_client(dev); 334bf4994d7SScott Wood struct ds1374 *ds1374 = i2c_get_clientdata(client); 33516380c15SJohn Stultz int ret; 336bf4994d7SScott Wood 337bf4994d7SScott Wood mutex_lock(&ds1374->mutex); 338bf4994d7SScott Wood 339bf4994d7SScott Wood ret = i2c_smbus_read_byte_data(client, DS1374_REG_CR); 340bf4994d7SScott Wood if (ret < 0) 341bf4994d7SScott Wood goto out; 342bf4994d7SScott Wood 34316380c15SJohn Stultz if (enabled) { 344bf4994d7SScott Wood ret |= DS1374_REG_CR_WACE | DS1374_REG_CR_AIE; 345bf4994d7SScott Wood ret &= ~DS1374_REG_CR_WDALM; 34616380c15SJohn Stultz } else { 34716380c15SJohn Stultz ret &= ~DS1374_REG_CR_WACE; 348bf4994d7SScott Wood } 34916380c15SJohn Stultz ret = i2c_smbus_write_byte_data(client, DS1374_REG_CR, ret); 350bf4994d7SScott Wood 351bf4994d7SScott Wood out: 352bf4994d7SScott Wood mutex_unlock(&ds1374->mutex); 353bf4994d7SScott Wood return ret; 354bf4994d7SScott Wood } 355920f91e5SSøren Andersen #endif 356bf4994d7SScott Wood 357bf4994d7SScott Wood static const struct rtc_class_ops ds1374_rtc_ops = { 358bf4994d7SScott Wood .read_time = ds1374_read_time, 359bf4994d7SScott Wood .set_time = ds1374_set_time, 360920f91e5SSøren Andersen #ifndef CONFIG_RTC_DRV_DS1374_WDT 361bf4994d7SScott Wood .read_alarm = ds1374_read_alarm, 362bf4994d7SScott Wood .set_alarm = ds1374_set_alarm, 36316380c15SJohn Stultz .alarm_irq_enable = ds1374_alarm_irq_enable, 364920f91e5SSøren Andersen #endif 365bf4994d7SScott Wood }; 366bf4994d7SScott Wood 367920f91e5SSøren Andersen #ifdef CONFIG_RTC_DRV_DS1374_WDT 368920f91e5SSøren Andersen /* 369920f91e5SSøren Andersen ***************************************************************************** 370920f91e5SSøren Andersen * 371920f91e5SSøren Andersen * Watchdog Driver 372920f91e5SSøren Andersen * 373920f91e5SSøren Andersen ***************************************************************************** 374920f91e5SSøren Andersen */ 375920f91e5SSøren Andersen /* Default margin */ 376d3de4bebSJohnson CH Chen (陳昭勳) #define TIMER_MARGIN_DEFAULT 32 377d3de4bebSJohnson CH Chen (陳昭勳) #define TIMER_MARGIN_MIN 1 378d3de4bebSJohnson CH Chen (陳昭勳) #define TIMER_MARGIN_MAX 4095 /* 24-bit value */ 379920f91e5SSøren Andersen 380d3de4bebSJohnson CH Chen (陳昭勳) static int wdt_margin; 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 384d3de4bebSJohnson CH Chen (陳昭勳) static bool nowayout = WATCHDOG_NOWAYOUT; 385d3de4bebSJohnson CH Chen (陳昭勳) module_param(nowayout, bool, 0); 386d3de4bebSJohnson CH Chen (陳昭勳) MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default =" 387d3de4bebSJohnson CH Chen (陳昭勳) __MODULE_STRING(WATCHDOG_NOWAYOUT)")"); 388d3de4bebSJohnson CH Chen (陳昭勳) 389920f91e5SSøren Andersen static const struct watchdog_info ds1374_wdt_info = { 390*3d6cfb36SAlexandre Belloni .identity = "DS1374 Watchdog", 391920f91e5SSøren Andersen .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | 392920f91e5SSøren Andersen WDIOF_MAGICCLOSE, 393920f91e5SSøren Andersen }; 394920f91e5SSøren Andersen 395d3de4bebSJohnson CH Chen (陳昭勳) static int ds1374_wdt_settimeout(struct watchdog_device *wdt, unsigned int timeout) 396920f91e5SSøren Andersen { 397d3de4bebSJohnson CH Chen (陳昭勳) struct ds1374 *ds1374 = watchdog_get_drvdata(wdt); 398d3de4bebSJohnson CH Chen (陳昭勳) struct i2c_client *client = ds1374->client; 399d3de4bebSJohnson CH Chen (陳昭勳) int ret, cr; 400920f91e5SSøren Andersen 401d3de4bebSJohnson CH Chen (陳昭勳) wdt->timeout = timeout; 402d3de4bebSJohnson CH Chen (陳昭勳) 403d3de4bebSJohnson CH Chen (陳昭勳) cr = i2c_smbus_read_byte_data(client, DS1374_REG_CR); 404d3de4bebSJohnson CH Chen (陳昭勳) if (cr < 0) 405d3de4bebSJohnson CH Chen (陳昭勳) return cr; 406920f91e5SSøren Andersen 407920f91e5SSøren Andersen /* Disable any existing watchdog/alarm before setting the new one */ 408920f91e5SSøren Andersen cr &= ~DS1374_REG_CR_WACE; 409920f91e5SSøren Andersen 410d3de4bebSJohnson CH Chen (陳昭勳) ret = i2c_smbus_write_byte_data(client, DS1374_REG_CR, cr); 411920f91e5SSøren Andersen if (ret < 0) 412d3de4bebSJohnson CH Chen (陳昭勳) return ret; 413920f91e5SSøren Andersen 414920f91e5SSøren Andersen /* Set new watchdog time */ 415d3de4bebSJohnson CH Chen (陳昭勳) timeout = timeout * 4096; 416d3de4bebSJohnson CH Chen (陳昭勳) ret = ds1374_write_rtc(client, timeout, DS1374_REG_WDALM0, 3); 417d3de4bebSJohnson CH Chen (陳昭勳) if (ret) 418d3de4bebSJohnson CH Chen (陳昭勳) return ret; 419920f91e5SSøren Andersen 420920f91e5SSøren Andersen /* Enable watchdog timer */ 421920f91e5SSøren Andersen cr |= DS1374_REG_CR_WACE | DS1374_REG_CR_WDALM; 422d3de4bebSJohnson CH Chen (陳昭勳) cr &= ~DS1374_REG_CR_WDSTR;/* for RST PIN */ 423920f91e5SSøren Andersen cr &= ~DS1374_REG_CR_AIE; 424920f91e5SSøren Andersen 425d3de4bebSJohnson CH Chen (陳昭勳) ret = i2c_smbus_write_byte_data(client, DS1374_REG_CR, cr); 426920f91e5SSøren Andersen if (ret < 0) 427d3de4bebSJohnson CH Chen (陳昭勳) return ret; 428920f91e5SSøren Andersen 429920f91e5SSøren Andersen return 0; 430920f91e5SSøren Andersen } 431920f91e5SSøren Andersen 432920f91e5SSøren Andersen /* 433920f91e5SSøren Andersen * Reload the watchdog timer. (ie, pat the watchdog) 434920f91e5SSøren Andersen */ 435d3de4bebSJohnson CH Chen (陳昭勳) static int ds1374_wdt_start(struct watchdog_device *wdt) 436920f91e5SSøren Andersen { 437d3de4bebSJohnson CH Chen (陳昭勳) struct ds1374 *ds1374 = watchdog_get_drvdata(wdt); 438920f91e5SSøren Andersen u32 val; 439920f91e5SSøren Andersen 440d3de4bebSJohnson CH Chen (陳昭勳) return ds1374_read_rtc(ds1374->client, &val, DS1374_REG_WDALM0, 3); 441920f91e5SSøren Andersen } 442920f91e5SSøren Andersen 443d3de4bebSJohnson CH Chen (陳昭勳) static int ds1374_wdt_stop(struct watchdog_device *wdt) 444920f91e5SSøren Andersen { 445d3de4bebSJohnson CH Chen (陳昭勳) struct ds1374 *ds1374 = watchdog_get_drvdata(wdt); 446d3de4bebSJohnson CH Chen (陳昭勳) struct i2c_client *client = ds1374->client; 447920f91e5SSøren Andersen int cr; 448920f91e5SSøren Andersen 449d3de4bebSJohnson CH Chen (陳昭勳) cr = i2c_smbus_read_byte_data(client, DS1374_REG_CR); 450d3de4bebSJohnson CH Chen (陳昭勳) if (cr < 0) 451d3de4bebSJohnson CH Chen (陳昭勳) return cr; 452d3de4bebSJohnson CH Chen (陳昭勳) 453920f91e5SSøren Andersen /* Disable watchdog timer */ 454920f91e5SSøren Andersen cr &= ~DS1374_REG_CR_WACE; 455920f91e5SSøren Andersen 456d3de4bebSJohnson CH Chen (陳昭勳) return i2c_smbus_write_byte_data(client, DS1374_REG_CR, cr); 457920f91e5SSøren Andersen } 458920f91e5SSøren Andersen 459d3de4bebSJohnson CH Chen (陳昭勳) static const struct watchdog_ops ds1374_wdt_ops = { 460920f91e5SSøren Andersen .owner = THIS_MODULE, 461d3de4bebSJohnson CH Chen (陳昭勳) .start = ds1374_wdt_start, 462d3de4bebSJohnson CH Chen (陳昭勳) .stop = ds1374_wdt_stop, 463d3de4bebSJohnson CH Chen (陳昭勳) .set_timeout = ds1374_wdt_settimeout, 464920f91e5SSøren Andersen }; 465920f91e5SSøren Andersen #endif /*CONFIG_RTC_DRV_DS1374_WDT*/ 466920f91e5SSøren Andersen /* 467920f91e5SSøren Andersen ***************************************************************************** 468920f91e5SSøren Andersen * 469920f91e5SSøren Andersen * Driver Interface 470920f91e5SSøren Andersen * 471920f91e5SSøren Andersen ***************************************************************************** 472920f91e5SSøren Andersen */ 473d2653e92SJean Delvare static int ds1374_probe(struct i2c_client *client, 474d2653e92SJean Delvare const struct i2c_device_id *id) 475bf4994d7SScott Wood { 476bf4994d7SScott Wood struct ds1374 *ds1374; 477bf4994d7SScott Wood int ret; 478bf4994d7SScott Wood 479d1a96639SSachin Kamat ds1374 = devm_kzalloc(&client->dev, sizeof(struct ds1374), GFP_KERNEL); 480bf4994d7SScott Wood if (!ds1374) 481bf4994d7SScott Wood return -ENOMEM; 482bf4994d7SScott Wood 483c11af813SAlexandre Belloni ds1374->rtc = devm_rtc_allocate_device(&client->dev); 484c11af813SAlexandre Belloni if (IS_ERR(ds1374->rtc)) 485c11af813SAlexandre Belloni return PTR_ERR(ds1374->rtc); 486c11af813SAlexandre Belloni 487bf4994d7SScott Wood ds1374->client = client; 488bf4994d7SScott Wood i2c_set_clientdata(client, ds1374); 489bf4994d7SScott Wood 490bf4994d7SScott Wood INIT_WORK(&ds1374->work, ds1374_work); 491bf4994d7SScott Wood mutex_init(&ds1374->mutex); 492bf4994d7SScott Wood 493bf4994d7SScott Wood ret = ds1374_check_rtc_status(client); 494bf4994d7SScott Wood if (ret) 495d1a96639SSachin Kamat return ret; 496bf4994d7SScott Wood 497b42f9317SAnton Vorontsov if (client->irq > 0) { 498d1a96639SSachin Kamat ret = devm_request_irq(&client->dev, client->irq, ds1374_irq, 0, 499bf4994d7SScott Wood "ds1374", client); 500bf4994d7SScott Wood if (ret) { 501bf4994d7SScott Wood dev_err(&client->dev, "unable to request IRQ\n"); 502d1a96639SSachin Kamat return ret; 503bf4994d7SScott Wood } 50426b3c01fSAnton Vorontsov 50526b3c01fSAnton Vorontsov device_set_wakeup_capable(&client->dev, 1); 506bf4994d7SScott Wood } 507bf4994d7SScott Wood 508c11af813SAlexandre Belloni ds1374->rtc->ops = &ds1374_rtc_ops; 5094136ff3aSAlexandre Belloni ds1374->rtc->range_max = U32_MAX; 510c11af813SAlexandre Belloni 511c11af813SAlexandre Belloni ret = rtc_register_device(ds1374->rtc); 512c11af813SAlexandre Belloni if (ret) 513c11af813SAlexandre Belloni return ret; 514bf4994d7SScott Wood 515920f91e5SSøren Andersen #ifdef CONFIG_RTC_DRV_DS1374_WDT 516d3de4bebSJohnson CH Chen (陳昭勳) ds1374->wdt.info = &ds1374_wdt_info; 517d3de4bebSJohnson CH Chen (陳昭勳) ds1374->wdt.ops = &ds1374_wdt_ops; 518d3de4bebSJohnson CH Chen (陳昭勳) ds1374->wdt.timeout = TIMER_MARGIN_DEFAULT; 519d3de4bebSJohnson CH Chen (陳昭勳) ds1374->wdt.min_timeout = TIMER_MARGIN_MIN; 520d3de4bebSJohnson CH Chen (陳昭勳) ds1374->wdt.max_timeout = TIMER_MARGIN_MAX; 521d3de4bebSJohnson CH Chen (陳昭勳) 522d3de4bebSJohnson CH Chen (陳昭勳) watchdog_init_timeout(&ds1374->wdt, wdt_margin, &client->dev); 523d3de4bebSJohnson CH Chen (陳昭勳) watchdog_set_nowayout(&ds1374->wdt, nowayout); 524d3de4bebSJohnson CH Chen (陳昭勳) watchdog_stop_on_reboot(&ds1374->wdt); 525d3de4bebSJohnson CH Chen (陳昭勳) watchdog_stop_on_unregister(&ds1374->wdt); 526d3de4bebSJohnson CH Chen (陳昭勳) watchdog_set_drvdata(&ds1374->wdt, ds1374); 527d3de4bebSJohnson CH Chen (陳昭勳) ds1374_wdt_settimeout(&ds1374->wdt, ds1374->wdt.timeout); 528d3de4bebSJohnson CH Chen (陳昭勳) 529d3de4bebSJohnson CH Chen (陳昭勳) ret = devm_watchdog_register_device(&client->dev, &ds1374->wdt); 530920f91e5SSøren Andersen if (ret) 531920f91e5SSøren Andersen return ret; 532920f91e5SSøren Andersen #endif 533920f91e5SSøren Andersen 534bf4994d7SScott Wood return 0; 535bf4994d7SScott Wood } 536bf4994d7SScott Wood 5375a167f45SGreg Kroah-Hartman static int ds1374_remove(struct i2c_client *client) 538bf4994d7SScott Wood { 539bf4994d7SScott Wood struct ds1374 *ds1374 = i2c_get_clientdata(client); 540bf4994d7SScott Wood 541b42f9317SAnton Vorontsov if (client->irq > 0) { 542bf4994d7SScott Wood mutex_lock(&ds1374->mutex); 543bf4994d7SScott Wood ds1374->exiting = 1; 544bf4994d7SScott Wood mutex_unlock(&ds1374->mutex); 545bf4994d7SScott Wood 546d1a96639SSachin Kamat devm_free_irq(&client->dev, client->irq, client); 5479db8995bSTejun Heo cancel_work_sync(&ds1374->work); 548bf4994d7SScott Wood } 549bf4994d7SScott Wood 550bf4994d7SScott Wood return 0; 551bf4994d7SScott Wood } 552bf4994d7SScott Wood 5538b80ef64SJingoo Han #ifdef CONFIG_PM_SLEEP 554bc96ba74SMark Brown static int ds1374_suspend(struct device *dev) 555986e36a5SMarc Pignat { 556bc96ba74SMark Brown struct i2c_client *client = to_i2c_client(dev); 557bc96ba74SMark Brown 5580d9030a2SOctavian Purdila if (client->irq > 0 && device_may_wakeup(&client->dev)) 559986e36a5SMarc Pignat enable_irq_wake(client->irq); 560986e36a5SMarc Pignat return 0; 561986e36a5SMarc Pignat } 562986e36a5SMarc Pignat 563bc96ba74SMark Brown static int ds1374_resume(struct device *dev) 564986e36a5SMarc Pignat { 565bc96ba74SMark Brown struct i2c_client *client = to_i2c_client(dev); 566bc96ba74SMark Brown 5670d9030a2SOctavian Purdila if (client->irq > 0 && device_may_wakeup(&client->dev)) 568986e36a5SMarc Pignat disable_irq_wake(client->irq); 569986e36a5SMarc Pignat return 0; 570986e36a5SMarc Pignat } 5718b80ef64SJingoo Han #endif 572bc96ba74SMark Brown 573bc96ba74SMark Brown static SIMPLE_DEV_PM_OPS(ds1374_pm, ds1374_suspend, ds1374_resume); 574bc96ba74SMark Brown 575bf4994d7SScott Wood static struct i2c_driver ds1374_driver = { 576bf4994d7SScott Wood .driver = { 577bf4994d7SScott Wood .name = "rtc-ds1374", 578abac12e1SJavier Martinez Canillas .of_match_table = of_match_ptr(ds1374_of_match), 5798b80ef64SJingoo Han .pm = &ds1374_pm, 580bf4994d7SScott Wood }, 581bf4994d7SScott Wood .probe = ds1374_probe, 5825a167f45SGreg Kroah-Hartman .remove = ds1374_remove, 5833760f736SJean Delvare .id_table = ds1374_id, 584bf4994d7SScott Wood }; 585bf4994d7SScott Wood 5860abc9201SAxel Lin module_i2c_driver(ds1374_driver); 587bf4994d7SScott Wood 588bf4994d7SScott Wood MODULE_AUTHOR("Scott Wood <scottwood@freescale.com>"); 589bf4994d7SScott Wood MODULE_DESCRIPTION("Maxim/Dallas DS1374 RTC Driver"); 590bf4994d7SScott Wood MODULE_LICENSE("GPL"); 591