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