167561a8eSNobuhiro Iwamatsu // SPDX-License-Identifier: GPL-2.0-only
2bf4994d7SScott Wood /*
3bf4994d7SScott Wood * RTC client/driver for the Maxim/Dallas DS1374 Real-Time Clock over I2C
4bf4994d7SScott Wood *
5bf4994d7SScott Wood * Based on code by Randy Vinson <rvinson@mvista.com>,
6bf4994d7SScott Wood * which was based on the m41t00.c by Mark Greer <mgreer@mvista.com>.
7bf4994d7SScott Wood *
8920f91e5SSøren Andersen * Copyright (C) 2014 Rose Technology
9bf4994d7SScott Wood * Copyright (C) 2006-2007 Freescale Semiconductor
1067561a8eSNobuhiro Iwamatsu * Copyright (c) 2005 MontaVista Software, Inc.
11bf4994d7SScott Wood */
12bf4994d7SScott Wood /*
13bf4994d7SScott Wood * It would be more efficient to use i2c msgs/i2c_transfer directly but, as
14ccf988b6SMauro Carvalho Chehab * recommended in .../Documentation/i2c/writing-clients.rst section
15bf4994d7SScott Wood * "Sending and receiving", using SMBus level communication is preferred.
16bf4994d7SScott Wood */
17bf4994d7SScott Wood
18a737e835SJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
19a737e835SJoe Perches
20bf4994d7SScott Wood #include <linux/kernel.h>
21bf4994d7SScott Wood #include <linux/module.h>
22bf4994d7SScott Wood #include <linux/interrupt.h>
23bf4994d7SScott Wood #include <linux/i2c.h>
24bf4994d7SScott Wood #include <linux/rtc.h>
25bf4994d7SScott Wood #include <linux/bcd.h>
26bf4994d7SScott Wood #include <linux/workqueue.h>
275a0e3ad6STejun Heo #include <linux/slab.h>
28bc96ba74SMark Brown #include <linux/pm.h>
29920f91e5SSøren Andersen #ifdef CONFIG_RTC_DRV_DS1374_WDT
30920f91e5SSøren Andersen #include <linux/fs.h>
31920f91e5SSøren Andersen #include <linux/ioctl.h>
32920f91e5SSøren Andersen #include <linux/miscdevice.h>
33920f91e5SSøren Andersen #include <linux/reboot.h>
34920f91e5SSøren Andersen #include <linux/watchdog.h>
35920f91e5SSøren Andersen #endif
36bf4994d7SScott Wood
37bf4994d7SScott Wood #define DS1374_REG_TOD0 0x00 /* Time of Day */
38bf4994d7SScott Wood #define DS1374_REG_TOD1 0x01
39bf4994d7SScott Wood #define DS1374_REG_TOD2 0x02
40bf4994d7SScott Wood #define DS1374_REG_TOD3 0x03
41bf4994d7SScott Wood #define DS1374_REG_WDALM0 0x04 /* Watchdog/Alarm */
42bf4994d7SScott Wood #define DS1374_REG_WDALM1 0x05
43bf4994d7SScott Wood #define DS1374_REG_WDALM2 0x06
44bf4994d7SScott Wood #define DS1374_REG_CR 0x07 /* Control */
45bf4994d7SScott Wood #define DS1374_REG_CR_AIE 0x01 /* Alarm Int. Enable */
46d3de4bebSJohnson CH Chen (陳昭勳) #define DS1374_REG_CR_WDSTR 0x08 /* 1=INT, 0=RST */
47bf4994d7SScott Wood #define DS1374_REG_CR_WDALM 0x20 /* 1=Watchdog, 0=Alarm */
48bf4994d7SScott Wood #define DS1374_REG_CR_WACE 0x40 /* WD/Alarm counter enable */
49bf4994d7SScott Wood #define DS1374_REG_SR 0x08 /* Status */
50bf4994d7SScott Wood #define DS1374_REG_SR_OSF 0x80 /* Oscillator Stop Flag */
51bf4994d7SScott Wood #define DS1374_REG_SR_AF 0x01 /* Alarm Flag */
52bf4994d7SScott Wood #define DS1374_REG_TCR 0x09 /* Trickle Charge */
53bf4994d7SScott Wood
543760f736SJean Delvare static const struct i2c_device_id ds1374_id[] = {
55f2eb4327SJean Delvare { "ds1374", 0 },
563760f736SJean Delvare { }
573760f736SJean Delvare };
583760f736SJean Delvare MODULE_DEVICE_TABLE(i2c, ds1374_id);
593760f736SJean Delvare
60920f91e5SSøren Andersen #ifdef CONFIG_OF
61920f91e5SSøren Andersen static const struct of_device_id ds1374_of_match[] = {
62920f91e5SSøren Andersen { .compatible = "dallas,ds1374" },
63920f91e5SSøren Andersen { }
64920f91e5SSøren Andersen };
65920f91e5SSøren Andersen MODULE_DEVICE_TABLE(of, ds1374_of_match);
66920f91e5SSøren Andersen #endif
67920f91e5SSøren Andersen
68bf4994d7SScott Wood struct ds1374 {
69bf4994d7SScott Wood struct i2c_client *client;
70bf4994d7SScott Wood struct rtc_device *rtc;
71bf4994d7SScott Wood struct work_struct work;
72d3de4bebSJohnson CH Chen (陳昭勳) #ifdef CONFIG_RTC_DRV_DS1374_WDT
73d3de4bebSJohnson CH Chen (陳昭勳) struct watchdog_device wdt;
74d3de4bebSJohnson CH Chen (陳昭勳) #endif
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
ds1374_read_rtc(struct i2c_client * client,u32 * time,int reg,int nbytes)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
ds1374_write_rtc(struct i2c_client * client,u32 time,int reg,int nbytes)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
ds1374_check_rtc_status(struct i2c_client * client)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
ds1374_read_time(struct device * dev,struct rtc_time * time)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)
167ca824be9SAlexandre Belloni rtc_time64_to_tm(itime, time);
168bf4994d7SScott Wood
169bf4994d7SScott Wood return ret;
170bf4994d7SScott Wood }
171bf4994d7SScott Wood
ds1374_set_time(struct device * dev,struct rtc_time * time)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);
175ca824be9SAlexandre Belloni unsigned long itime = rtc_tm_to_time64(time);
176bf4994d7SScott Wood
177bf4994d7SScott Wood return ds1374_write_rtc(client, itime, DS1374_REG_TOD0, 4);
178bf4994d7SScott Wood }
179bf4994d7SScott Wood
180920f91e5SSøren Andersen #ifndef CONFIG_RTC_DRV_DS1374_WDT
181bf4994d7SScott Wood /* The ds1374 has a decrementer for an alarm, rather than a comparator.
182bf4994d7SScott Wood * If the time of day is changed, then the alarm will need to be
183bf4994d7SScott Wood * reset.
184bf4994d7SScott Wood */
ds1374_read_alarm(struct device * dev,struct rtc_wkalrm * alarm)185bf4994d7SScott Wood static int ds1374_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
186bf4994d7SScott Wood {
187bf4994d7SScott Wood struct i2c_client *client = to_i2c_client(dev);
188bf4994d7SScott Wood struct ds1374 *ds1374 = i2c_get_clientdata(client);
189bf4994d7SScott Wood u32 now, cur_alarm;
190bf4994d7SScott Wood int cr, sr;
191bf4994d7SScott Wood int ret = 0;
192bf4994d7SScott Wood
193b42f9317SAnton Vorontsov if (client->irq <= 0)
194bf4994d7SScott Wood return -EINVAL;
195bf4994d7SScott Wood
196bf4994d7SScott Wood mutex_lock(&ds1374->mutex);
197bf4994d7SScott Wood
198bf4994d7SScott Wood cr = ret = i2c_smbus_read_byte_data(client, DS1374_REG_CR);
199bf4994d7SScott Wood if (ret < 0)
200bf4994d7SScott Wood goto out;
201bf4994d7SScott Wood
202bf4994d7SScott Wood sr = ret = i2c_smbus_read_byte_data(client, DS1374_REG_SR);
203bf4994d7SScott Wood if (ret < 0)
204bf4994d7SScott Wood goto out;
205bf4994d7SScott Wood
206bf4994d7SScott Wood ret = ds1374_read_rtc(client, &now, DS1374_REG_TOD0, 4);
207bf4994d7SScott Wood if (ret)
208bf4994d7SScott Wood goto out;
209bf4994d7SScott Wood
210bf4994d7SScott Wood ret = ds1374_read_rtc(client, &cur_alarm, DS1374_REG_WDALM0, 3);
211bf4994d7SScott Wood if (ret)
212bf4994d7SScott Wood goto out;
213bf4994d7SScott Wood
214ca824be9SAlexandre Belloni rtc_time64_to_tm(now + cur_alarm, &alarm->time);
215bf4994d7SScott Wood alarm->enabled = !!(cr & DS1374_REG_CR_WACE);
216bf4994d7SScott Wood alarm->pending = !!(sr & DS1374_REG_SR_AF);
217bf4994d7SScott Wood
218bf4994d7SScott Wood out:
219bf4994d7SScott Wood mutex_unlock(&ds1374->mutex);
220bf4994d7SScott Wood return ret;
221bf4994d7SScott Wood }
222bf4994d7SScott Wood
ds1374_set_alarm(struct device * dev,struct rtc_wkalrm * alarm)223bf4994d7SScott Wood static int ds1374_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
224bf4994d7SScott Wood {
225bf4994d7SScott Wood struct i2c_client *client = to_i2c_client(dev);
226bf4994d7SScott Wood struct ds1374 *ds1374 = i2c_get_clientdata(client);
227bf4994d7SScott Wood struct rtc_time now;
228bf4994d7SScott Wood unsigned long new_alarm, itime;
229bf4994d7SScott Wood int cr;
230bf4994d7SScott Wood int ret = 0;
231bf4994d7SScott Wood
232b42f9317SAnton Vorontsov if (client->irq <= 0)
233bf4994d7SScott Wood return -EINVAL;
234bf4994d7SScott Wood
235bf4994d7SScott Wood ret = ds1374_read_time(dev, &now);
236bf4994d7SScott Wood if (ret < 0)
237bf4994d7SScott Wood return ret;
238bf4994d7SScott Wood
239ca824be9SAlexandre Belloni new_alarm = rtc_tm_to_time64(&alarm->time);
240ca824be9SAlexandre Belloni itime = rtc_tm_to_time64(&now);
241bf4994d7SScott Wood
242bf4994d7SScott Wood /* This can happen due to races, in addition to dates that are
243bf4994d7SScott Wood * truly in the past. To avoid requiring the caller to check for
244bf4994d7SScott Wood * races, dates in the past are assumed to be in the recent past
245bf4994d7SScott Wood * (i.e. not something that we'd rather the caller know about via
246bf4994d7SScott Wood * an error), and the alarm is set to go off as soon as possible.
247bf4994d7SScott Wood */
248fa7af8b1SRoel Kluin if (time_before_eq(new_alarm, itime))
249bf4994d7SScott Wood new_alarm = 1;
250fa7af8b1SRoel Kluin else
251fa7af8b1SRoel Kluin new_alarm -= itime;
252bf4994d7SScott Wood
253bf4994d7SScott Wood mutex_lock(&ds1374->mutex);
254bf4994d7SScott Wood
255bf4994d7SScott Wood ret = cr = i2c_smbus_read_byte_data(client, DS1374_REG_CR);
256bf4994d7SScott Wood if (ret < 0)
257bf4994d7SScott Wood goto out;
258bf4994d7SScott Wood
259bf4994d7SScott Wood /* Disable any existing alarm before setting the new one
260bf4994d7SScott Wood * (or lack thereof). */
261bf4994d7SScott Wood cr &= ~DS1374_REG_CR_WACE;
262bf4994d7SScott Wood
263bf4994d7SScott Wood ret = i2c_smbus_write_byte_data(client, DS1374_REG_CR, cr);
264bf4994d7SScott Wood if (ret < 0)
265bf4994d7SScott Wood goto out;
266bf4994d7SScott Wood
267bf4994d7SScott Wood ret = ds1374_write_rtc(client, new_alarm, DS1374_REG_WDALM0, 3);
268bf4994d7SScott Wood if (ret)
269bf4994d7SScott Wood goto out;
270bf4994d7SScott Wood
271bf4994d7SScott Wood if (alarm->enabled) {
272bf4994d7SScott Wood cr |= DS1374_REG_CR_WACE | DS1374_REG_CR_AIE;
273bf4994d7SScott Wood cr &= ~DS1374_REG_CR_WDALM;
274bf4994d7SScott Wood
275bf4994d7SScott Wood ret = i2c_smbus_write_byte_data(client, DS1374_REG_CR, cr);
276bf4994d7SScott Wood }
277bf4994d7SScott Wood
278bf4994d7SScott Wood out:
279bf4994d7SScott Wood mutex_unlock(&ds1374->mutex);
280bf4994d7SScott Wood return ret;
281bf4994d7SScott Wood }
282920f91e5SSøren Andersen #endif
283bf4994d7SScott Wood
ds1374_irq(int irq,void * dev_id)284bf4994d7SScott Wood static irqreturn_t ds1374_irq(int irq, void *dev_id)
285bf4994d7SScott Wood {
286bf4994d7SScott Wood struct i2c_client *client = dev_id;
287bf4994d7SScott Wood struct ds1374 *ds1374 = i2c_get_clientdata(client);
288bf4994d7SScott Wood
289bf4994d7SScott Wood disable_irq_nosync(irq);
290bf4994d7SScott Wood schedule_work(&ds1374->work);
291bf4994d7SScott Wood return IRQ_HANDLED;
292bf4994d7SScott Wood }
293bf4994d7SScott Wood
ds1374_work(struct work_struct * work)294bf4994d7SScott Wood static void ds1374_work(struct work_struct *work)
295bf4994d7SScott Wood {
296bf4994d7SScott Wood struct ds1374 *ds1374 = container_of(work, struct ds1374, work);
297bf4994d7SScott Wood struct i2c_client *client = ds1374->client;
298bf4994d7SScott Wood int stat, control;
299bf4994d7SScott Wood
300bf4994d7SScott Wood mutex_lock(&ds1374->mutex);
301bf4994d7SScott Wood
302bf4994d7SScott Wood stat = i2c_smbus_read_byte_data(client, DS1374_REG_SR);
303bf4994d7SScott Wood if (stat < 0)
30428df30e6SJiri Slaby goto unlock;
305bf4994d7SScott Wood
306bf4994d7SScott Wood if (stat & DS1374_REG_SR_AF) {
307bf4994d7SScott Wood stat &= ~DS1374_REG_SR_AF;
308bf4994d7SScott Wood i2c_smbus_write_byte_data(client, DS1374_REG_SR, stat);
309bf4994d7SScott Wood
310bf4994d7SScott Wood control = i2c_smbus_read_byte_data(client, DS1374_REG_CR);
311bf4994d7SScott Wood if (control < 0)
312bf4994d7SScott Wood goto out;
313bf4994d7SScott Wood
314bf4994d7SScott Wood control &= ~(DS1374_REG_CR_WACE | DS1374_REG_CR_AIE);
315bf4994d7SScott Wood i2c_smbus_write_byte_data(client, DS1374_REG_CR, control);
316bf4994d7SScott Wood
317bf4994d7SScott Wood rtc_update_irq(ds1374->rtc, 1, RTC_AF | RTC_IRQF);
318bf4994d7SScott Wood }
319bf4994d7SScott Wood
320bf4994d7SScott Wood out:
321bf4994d7SScott Wood if (!ds1374->exiting)
322bf4994d7SScott Wood enable_irq(client->irq);
32328df30e6SJiri Slaby unlock:
324bf4994d7SScott Wood mutex_unlock(&ds1374->mutex);
325bf4994d7SScott Wood }
326bf4994d7SScott Wood
327920f91e5SSøren Andersen #ifndef CONFIG_RTC_DRV_DS1374_WDT
ds1374_alarm_irq_enable(struct device * dev,unsigned int enabled)32816380c15SJohn Stultz static int ds1374_alarm_irq_enable(struct device *dev, unsigned int enabled)
329bf4994d7SScott Wood {
330bf4994d7SScott Wood struct i2c_client *client = to_i2c_client(dev);
331bf4994d7SScott Wood struct ds1374 *ds1374 = i2c_get_clientdata(client);
33216380c15SJohn Stultz int ret;
333bf4994d7SScott Wood
334bf4994d7SScott Wood mutex_lock(&ds1374->mutex);
335bf4994d7SScott Wood
336bf4994d7SScott Wood ret = i2c_smbus_read_byte_data(client, DS1374_REG_CR);
337bf4994d7SScott Wood if (ret < 0)
338bf4994d7SScott Wood goto out;
339bf4994d7SScott Wood
34016380c15SJohn Stultz if (enabled) {
341bf4994d7SScott Wood ret |= DS1374_REG_CR_WACE | DS1374_REG_CR_AIE;
342bf4994d7SScott Wood ret &= ~DS1374_REG_CR_WDALM;
34316380c15SJohn Stultz } else {
34416380c15SJohn Stultz ret &= ~DS1374_REG_CR_WACE;
345bf4994d7SScott Wood }
34616380c15SJohn Stultz ret = i2c_smbus_write_byte_data(client, DS1374_REG_CR, ret);
347bf4994d7SScott Wood
348bf4994d7SScott Wood out:
349bf4994d7SScott Wood mutex_unlock(&ds1374->mutex);
350bf4994d7SScott Wood return ret;
351bf4994d7SScott Wood }
352920f91e5SSøren Andersen #endif
353bf4994d7SScott Wood
354bf4994d7SScott Wood static const struct rtc_class_ops ds1374_rtc_ops = {
355bf4994d7SScott Wood .read_time = ds1374_read_time,
356bf4994d7SScott Wood .set_time = ds1374_set_time,
357920f91e5SSøren Andersen #ifndef CONFIG_RTC_DRV_DS1374_WDT
358bf4994d7SScott Wood .read_alarm = ds1374_read_alarm,
359bf4994d7SScott Wood .set_alarm = ds1374_set_alarm,
36016380c15SJohn Stultz .alarm_irq_enable = ds1374_alarm_irq_enable,
361920f91e5SSøren Andersen #endif
362bf4994d7SScott Wood };
363bf4994d7SScott Wood
364920f91e5SSøren Andersen #ifdef CONFIG_RTC_DRV_DS1374_WDT
365920f91e5SSøren Andersen /*
366920f91e5SSøren Andersen *****************************************************************************
367920f91e5SSøren Andersen *
368920f91e5SSøren Andersen * Watchdog Driver
369920f91e5SSøren Andersen *
370920f91e5SSøren Andersen *****************************************************************************
371920f91e5SSøren Andersen */
372920f91e5SSøren Andersen /* Default margin */
373d3de4bebSJohnson CH Chen (陳昭勳) #define TIMER_MARGIN_DEFAULT 32
374d3de4bebSJohnson CH Chen (陳昭勳) #define TIMER_MARGIN_MIN 1
375d3de4bebSJohnson CH Chen (陳昭勳) #define TIMER_MARGIN_MAX 4095 /* 24-bit value */
376920f91e5SSøren Andersen
377d3de4bebSJohnson CH Chen (陳昭勳) static int wdt_margin;
378920f91e5SSøren Andersen module_param(wdt_margin, int, 0);
379920f91e5SSøren Andersen MODULE_PARM_DESC(wdt_margin, "Watchdog timeout in seconds (default 32s)");
380920f91e5SSøren Andersen
381d3de4bebSJohnson CH Chen (陳昭勳) static bool nowayout = WATCHDOG_NOWAYOUT;
382d3de4bebSJohnson CH Chen (陳昭勳) module_param(nowayout, bool, 0);
383d3de4bebSJohnson CH Chen (陳昭勳) MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default ="
384d3de4bebSJohnson CH Chen (陳昭勳) __MODULE_STRING(WATCHDOG_NOWAYOUT)")");
385d3de4bebSJohnson CH Chen (陳昭勳)
386920f91e5SSøren Andersen static const struct watchdog_info ds1374_wdt_info = {
3873d6cfb36SAlexandre Belloni .identity = "DS1374 Watchdog",
388920f91e5SSøren Andersen .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
389920f91e5SSøren Andersen WDIOF_MAGICCLOSE,
390920f91e5SSøren Andersen };
391920f91e5SSøren Andersen
ds1374_wdt_settimeout(struct watchdog_device * wdt,unsigned int timeout)392d3de4bebSJohnson CH Chen (陳昭勳) static int ds1374_wdt_settimeout(struct watchdog_device *wdt, unsigned int timeout)
393920f91e5SSøren Andersen {
394d3de4bebSJohnson CH Chen (陳昭勳) struct ds1374 *ds1374 = watchdog_get_drvdata(wdt);
395d3de4bebSJohnson CH Chen (陳昭勳) struct i2c_client *client = ds1374->client;
396d3de4bebSJohnson CH Chen (陳昭勳) int ret, cr;
397920f91e5SSøren Andersen
398d3de4bebSJohnson CH Chen (陳昭勳) wdt->timeout = timeout;
399d3de4bebSJohnson CH Chen (陳昭勳)
400d3de4bebSJohnson CH Chen (陳昭勳) cr = i2c_smbus_read_byte_data(client, DS1374_REG_CR);
401d3de4bebSJohnson CH Chen (陳昭勳) if (cr < 0)
402d3de4bebSJohnson CH Chen (陳昭勳) return cr;
403920f91e5SSøren Andersen
404920f91e5SSøren Andersen /* Disable any existing watchdog/alarm before setting the new one */
405920f91e5SSøren Andersen cr &= ~DS1374_REG_CR_WACE;
406920f91e5SSøren Andersen
407d3de4bebSJohnson CH Chen (陳昭勳) ret = i2c_smbus_write_byte_data(client, DS1374_REG_CR, cr);
408920f91e5SSøren Andersen if (ret < 0)
409d3de4bebSJohnson CH Chen (陳昭勳) return ret;
410920f91e5SSøren Andersen
411920f91e5SSøren Andersen /* Set new watchdog time */
412d3de4bebSJohnson CH Chen (陳昭勳) timeout = timeout * 4096;
413d3de4bebSJohnson CH Chen (陳昭勳) ret = ds1374_write_rtc(client, timeout, DS1374_REG_WDALM0, 3);
414d3de4bebSJohnson CH Chen (陳昭勳) if (ret)
415d3de4bebSJohnson CH Chen (陳昭勳) return ret;
416920f91e5SSøren Andersen
417920f91e5SSøren Andersen /* Enable watchdog timer */
418920f91e5SSøren Andersen cr |= DS1374_REG_CR_WACE | DS1374_REG_CR_WDALM;
419d3de4bebSJohnson CH Chen (陳昭勳) cr &= ~DS1374_REG_CR_WDSTR;/* for RST PIN */
420920f91e5SSøren Andersen cr &= ~DS1374_REG_CR_AIE;
421920f91e5SSøren Andersen
422d3de4bebSJohnson CH Chen (陳昭勳) ret = i2c_smbus_write_byte_data(client, DS1374_REG_CR, cr);
423920f91e5SSøren Andersen if (ret < 0)
424d3de4bebSJohnson CH Chen (陳昭勳) return ret;
425920f91e5SSøren Andersen
426920f91e5SSøren Andersen return 0;
427920f91e5SSøren Andersen }
428920f91e5SSøren Andersen
429920f91e5SSøren Andersen /*
430920f91e5SSøren Andersen * Reload the watchdog timer. (ie, pat the watchdog)
431920f91e5SSøren Andersen */
ds1374_wdt_start(struct watchdog_device * wdt)432d3de4bebSJohnson CH Chen (陳昭勳) static int ds1374_wdt_start(struct watchdog_device *wdt)
433920f91e5SSøren Andersen {
434d3de4bebSJohnson CH Chen (陳昭勳) struct ds1374 *ds1374 = watchdog_get_drvdata(wdt);
435920f91e5SSøren Andersen u32 val;
436920f91e5SSøren Andersen
437d3de4bebSJohnson CH Chen (陳昭勳) return ds1374_read_rtc(ds1374->client, &val, DS1374_REG_WDALM0, 3);
438920f91e5SSøren Andersen }
439920f91e5SSøren Andersen
ds1374_wdt_stop(struct watchdog_device * wdt)440d3de4bebSJohnson CH Chen (陳昭勳) static int ds1374_wdt_stop(struct watchdog_device *wdt)
441920f91e5SSøren Andersen {
442d3de4bebSJohnson CH Chen (陳昭勳) struct ds1374 *ds1374 = watchdog_get_drvdata(wdt);
443d3de4bebSJohnson CH Chen (陳昭勳) struct i2c_client *client = ds1374->client;
444920f91e5SSøren Andersen int cr;
445920f91e5SSøren Andersen
446d3de4bebSJohnson CH Chen (陳昭勳) cr = i2c_smbus_read_byte_data(client, DS1374_REG_CR);
447d3de4bebSJohnson CH Chen (陳昭勳) if (cr < 0)
448d3de4bebSJohnson CH Chen (陳昭勳) return cr;
449d3de4bebSJohnson CH Chen (陳昭勳)
450920f91e5SSøren Andersen /* Disable watchdog timer */
451920f91e5SSøren Andersen cr &= ~DS1374_REG_CR_WACE;
452920f91e5SSøren Andersen
453d3de4bebSJohnson CH Chen (陳昭勳) return i2c_smbus_write_byte_data(client, DS1374_REG_CR, cr);
454920f91e5SSøren Andersen }
455920f91e5SSøren Andersen
456d3de4bebSJohnson CH Chen (陳昭勳) static const struct watchdog_ops ds1374_wdt_ops = {
457920f91e5SSøren Andersen .owner = THIS_MODULE,
458d3de4bebSJohnson CH Chen (陳昭勳) .start = ds1374_wdt_start,
459d3de4bebSJohnson CH Chen (陳昭勳) .stop = ds1374_wdt_stop,
460d3de4bebSJohnson CH Chen (陳昭勳) .set_timeout = ds1374_wdt_settimeout,
461920f91e5SSøren Andersen };
462920f91e5SSøren Andersen #endif /*CONFIG_RTC_DRV_DS1374_WDT*/
463920f91e5SSøren Andersen /*
464920f91e5SSøren Andersen *****************************************************************************
465920f91e5SSøren Andersen *
466920f91e5SSøren Andersen * Driver Interface
467920f91e5SSøren Andersen *
468920f91e5SSøren Andersen *****************************************************************************
469920f91e5SSøren Andersen */
ds1374_probe(struct i2c_client * client)4703f4a3322SStephen Kitt static int ds1374_probe(struct i2c_client *client)
471bf4994d7SScott Wood {
472bf4994d7SScott Wood struct ds1374 *ds1374;
473bf4994d7SScott Wood int ret;
474bf4994d7SScott Wood
475d1a96639SSachin Kamat ds1374 = devm_kzalloc(&client->dev, sizeof(struct ds1374), GFP_KERNEL);
476bf4994d7SScott Wood if (!ds1374)
477bf4994d7SScott Wood return -ENOMEM;
478bf4994d7SScott Wood
479c11af813SAlexandre Belloni ds1374->rtc = devm_rtc_allocate_device(&client->dev);
480c11af813SAlexandre Belloni if (IS_ERR(ds1374->rtc))
481c11af813SAlexandre Belloni return PTR_ERR(ds1374->rtc);
482c11af813SAlexandre Belloni
483bf4994d7SScott Wood ds1374->client = client;
484bf4994d7SScott Wood i2c_set_clientdata(client, ds1374);
485bf4994d7SScott Wood
486bf4994d7SScott Wood INIT_WORK(&ds1374->work, ds1374_work);
487bf4994d7SScott Wood mutex_init(&ds1374->mutex);
488bf4994d7SScott Wood
489bf4994d7SScott Wood ret = ds1374_check_rtc_status(client);
490bf4994d7SScott Wood if (ret)
491d1a96639SSachin Kamat return ret;
492bf4994d7SScott Wood
493b42f9317SAnton Vorontsov if (client->irq > 0) {
494d1a96639SSachin Kamat ret = devm_request_irq(&client->dev, client->irq, ds1374_irq, 0,
495bf4994d7SScott Wood "ds1374", client);
496bf4994d7SScott Wood if (ret) {
497bf4994d7SScott Wood dev_err(&client->dev, "unable to request IRQ\n");
498d1a96639SSachin Kamat return ret;
499bf4994d7SScott Wood }
50026b3c01fSAnton Vorontsov
50126b3c01fSAnton Vorontsov device_set_wakeup_capable(&client->dev, 1);
502bf4994d7SScott Wood }
503bf4994d7SScott Wood
504c11af813SAlexandre Belloni ds1374->rtc->ops = &ds1374_rtc_ops;
5054136ff3aSAlexandre Belloni ds1374->rtc->range_max = U32_MAX;
506c11af813SAlexandre Belloni
507fdcfd854SBartosz Golaszewski ret = devm_rtc_register_device(ds1374->rtc);
508c11af813SAlexandre Belloni if (ret)
509c11af813SAlexandre Belloni return ret;
510bf4994d7SScott Wood
511920f91e5SSøren Andersen #ifdef CONFIG_RTC_DRV_DS1374_WDT
512d3de4bebSJohnson CH Chen (陳昭勳) ds1374->wdt.info = &ds1374_wdt_info;
513d3de4bebSJohnson CH Chen (陳昭勳) ds1374->wdt.ops = &ds1374_wdt_ops;
514d3de4bebSJohnson CH Chen (陳昭勳) ds1374->wdt.timeout = TIMER_MARGIN_DEFAULT;
515d3de4bebSJohnson CH Chen (陳昭勳) ds1374->wdt.min_timeout = TIMER_MARGIN_MIN;
516d3de4bebSJohnson CH Chen (陳昭勳) ds1374->wdt.max_timeout = TIMER_MARGIN_MAX;
517d3de4bebSJohnson CH Chen (陳昭勳)
518d3de4bebSJohnson CH Chen (陳昭勳) watchdog_init_timeout(&ds1374->wdt, wdt_margin, &client->dev);
519d3de4bebSJohnson CH Chen (陳昭勳) watchdog_set_nowayout(&ds1374->wdt, nowayout);
520d3de4bebSJohnson CH Chen (陳昭勳) watchdog_stop_on_reboot(&ds1374->wdt);
521d3de4bebSJohnson CH Chen (陳昭勳) watchdog_stop_on_unregister(&ds1374->wdt);
522d3de4bebSJohnson CH Chen (陳昭勳) watchdog_set_drvdata(&ds1374->wdt, ds1374);
523d3de4bebSJohnson CH Chen (陳昭勳) ds1374_wdt_settimeout(&ds1374->wdt, ds1374->wdt.timeout);
524d3de4bebSJohnson CH Chen (陳昭勳)
525d3de4bebSJohnson CH Chen (陳昭勳) ret = devm_watchdog_register_device(&client->dev, &ds1374->wdt);
526920f91e5SSøren Andersen if (ret)
527920f91e5SSøren Andersen return ret;
528920f91e5SSøren Andersen #endif
529920f91e5SSøren Andersen
530bf4994d7SScott Wood return 0;
531bf4994d7SScott Wood }
532bf4994d7SScott Wood
ds1374_remove(struct i2c_client * client)533ed5c2f5fSUwe Kleine-König static void ds1374_remove(struct i2c_client *client)
534bf4994d7SScott Wood {
535bf4994d7SScott Wood struct ds1374 *ds1374 = i2c_get_clientdata(client);
536bf4994d7SScott Wood
537b42f9317SAnton Vorontsov if (client->irq > 0) {
538bf4994d7SScott Wood mutex_lock(&ds1374->mutex);
539bf4994d7SScott Wood ds1374->exiting = 1;
540bf4994d7SScott Wood mutex_unlock(&ds1374->mutex);
541bf4994d7SScott Wood
542d1a96639SSachin Kamat devm_free_irq(&client->dev, client->irq, client);
5439db8995bSTejun Heo cancel_work_sync(&ds1374->work);
544bf4994d7SScott Wood }
545bf4994d7SScott Wood }
546bf4994d7SScott Wood
5478b80ef64SJingoo Han #ifdef CONFIG_PM_SLEEP
ds1374_suspend(struct device * dev)548bc96ba74SMark Brown static int ds1374_suspend(struct device *dev)
549986e36a5SMarc Pignat {
550bc96ba74SMark Brown struct i2c_client *client = to_i2c_client(dev);
551bc96ba74SMark Brown
5520d9030a2SOctavian Purdila if (client->irq > 0 && device_may_wakeup(&client->dev))
553986e36a5SMarc Pignat enable_irq_wake(client->irq);
554986e36a5SMarc Pignat return 0;
555986e36a5SMarc Pignat }
556986e36a5SMarc Pignat
ds1374_resume(struct device * dev)557bc96ba74SMark Brown static int ds1374_resume(struct device *dev)
558986e36a5SMarc Pignat {
559bc96ba74SMark Brown struct i2c_client *client = to_i2c_client(dev);
560bc96ba74SMark Brown
5610d9030a2SOctavian Purdila if (client->irq > 0 && device_may_wakeup(&client->dev))
562986e36a5SMarc Pignat disable_irq_wake(client->irq);
563986e36a5SMarc Pignat return 0;
564986e36a5SMarc Pignat }
5658b80ef64SJingoo Han #endif
566bc96ba74SMark Brown
567bc96ba74SMark Brown static SIMPLE_DEV_PM_OPS(ds1374_pm, ds1374_suspend, ds1374_resume);
568bc96ba74SMark Brown
569bf4994d7SScott Wood static struct i2c_driver ds1374_driver = {
570bf4994d7SScott Wood .driver = {
571bf4994d7SScott Wood .name = "rtc-ds1374",
572abac12e1SJavier Martinez Canillas .of_match_table = of_match_ptr(ds1374_of_match),
5738b80ef64SJingoo Han .pm = &ds1374_pm,
574bf4994d7SScott Wood },
575*31b0cecbSUwe Kleine-König .probe = ds1374_probe,
5765a167f45SGreg Kroah-Hartman .remove = ds1374_remove,
5773760f736SJean Delvare .id_table = ds1374_id,
578bf4994d7SScott Wood };
579bf4994d7SScott Wood
5800abc9201SAxel Lin module_i2c_driver(ds1374_driver);
581bf4994d7SScott Wood
582bf4994d7SScott Wood MODULE_AUTHOR("Scott Wood <scottwood@freescale.com>");
583bf4994d7SScott Wood MODULE_DESCRIPTION("Maxim/Dallas DS1374 RTC Driver");
584bf4994d7SScott Wood MODULE_LICENSE("GPL");
585