xref: /linux/drivers/rtc/rtc-ds1374.c (revision fdcfd854333be5b30377dc5daa9cd0fa1643a979)
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 = {
3903d6cfb36SAlexandre 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 
511fdcfd854SBartosz Golaszewski 	ret = devm_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