xref: /linux/drivers/rtc/rtc-ds1374.c (revision abac12e1105e7f3572f641db7583547069c1deaf)
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