xref: /openbmc/linux/drivers/rtc/rtc-wm8350.c (revision d844c64b)
12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2077eaf5bSMark Brown /*
3077eaf5bSMark Brown  *	Real Time Clock driver for Wolfson Microelectronics WM8350
4077eaf5bSMark Brown  *
5077eaf5bSMark Brown  *	Copyright (C) 2007, 2008 Wolfson Microelectronics PLC.
6077eaf5bSMark Brown  *
7077eaf5bSMark Brown  *  Author: Liam Girdwood
8077eaf5bSMark Brown  *          linux@wolfsonmicro.com
9077eaf5bSMark Brown  */
10077eaf5bSMark Brown 
11077eaf5bSMark Brown #include <linux/module.h>
12077eaf5bSMark Brown #include <linux/kernel.h>
13077eaf5bSMark Brown #include <linux/time.h>
14077eaf5bSMark Brown #include <linux/rtc.h>
15077eaf5bSMark Brown #include <linux/bcd.h>
16077eaf5bSMark Brown #include <linux/interrupt.h>
17077eaf5bSMark Brown #include <linux/ioctl.h>
18077eaf5bSMark Brown #include <linux/completion.h>
19077eaf5bSMark Brown #include <linux/mfd/wm8350/rtc.h>
20077eaf5bSMark Brown #include <linux/mfd/wm8350/core.h>
21077eaf5bSMark Brown #include <linux/delay.h>
22077eaf5bSMark Brown #include <linux/platform_device.h>
23077eaf5bSMark Brown 
24077eaf5bSMark Brown #define WM8350_SET_ALM_RETRIES	5
25077eaf5bSMark Brown #define WM8350_SET_TIME_RETRIES	5
26077eaf5bSMark Brown #define WM8350_GET_TIME_RETRIES	5
27077eaf5bSMark Brown 
28077eaf5bSMark Brown /*
29077eaf5bSMark Brown  * Read current time and date in RTC
30077eaf5bSMark Brown  */
wm8350_rtc_readtime(struct device * dev,struct rtc_time * tm)31077eaf5bSMark Brown static int wm8350_rtc_readtime(struct device *dev, struct rtc_time *tm)
32077eaf5bSMark Brown {
33077eaf5bSMark Brown 	struct wm8350 *wm8350 = dev_get_drvdata(dev);
34077eaf5bSMark Brown 	u16 time1[4], time2[4];
35077eaf5bSMark Brown 	int retries = WM8350_GET_TIME_RETRIES, ret;
36077eaf5bSMark Brown 
37077eaf5bSMark Brown 	/*
38077eaf5bSMark Brown 	 * Read the time twice and compare.
39077eaf5bSMark Brown 	 * If time1 == time2, then time is valid else retry.
40077eaf5bSMark Brown 	 */
41077eaf5bSMark Brown 	do {
42077eaf5bSMark Brown 		ret = wm8350_block_read(wm8350, WM8350_RTC_SECONDS_MINUTES,
43077eaf5bSMark Brown 					4, time1);
44077eaf5bSMark Brown 		if (ret < 0)
45077eaf5bSMark Brown 			return ret;
46077eaf5bSMark Brown 		ret = wm8350_block_read(wm8350, WM8350_RTC_SECONDS_MINUTES,
47077eaf5bSMark Brown 					4, time2);
48077eaf5bSMark Brown 		if (ret < 0)
49077eaf5bSMark Brown 			return ret;
50077eaf5bSMark Brown 
51077eaf5bSMark Brown 		if (memcmp(time1, time2, sizeof(time1)) == 0) {
52077eaf5bSMark Brown 			tm->tm_sec = time1[0] & WM8350_RTC_SECS_MASK;
53077eaf5bSMark Brown 
54077eaf5bSMark Brown 			tm->tm_min = (time1[0] & WM8350_RTC_MINS_MASK)
55077eaf5bSMark Brown 			    >> WM8350_RTC_MINS_SHIFT;
56077eaf5bSMark Brown 
57077eaf5bSMark Brown 			tm->tm_hour = time1[1] & WM8350_RTC_HRS_MASK;
58077eaf5bSMark Brown 
59077eaf5bSMark Brown 			tm->tm_wday = ((time1[1] >> WM8350_RTC_DAY_SHIFT)
60077eaf5bSMark Brown 				       & 0x7) - 1;
61077eaf5bSMark Brown 
62077eaf5bSMark Brown 			tm->tm_mon = ((time1[2] & WM8350_RTC_MTH_MASK)
63077eaf5bSMark Brown 				      >> WM8350_RTC_MTH_SHIFT) - 1;
64077eaf5bSMark Brown 
65077eaf5bSMark Brown 			tm->tm_mday = (time1[2] & WM8350_RTC_DATE_MASK);
66077eaf5bSMark Brown 
67077eaf5bSMark Brown 			tm->tm_year = ((time1[3] & WM8350_RTC_YHUNDREDS_MASK)
68077eaf5bSMark Brown 				       >> WM8350_RTC_YHUNDREDS_SHIFT) * 100;
69077eaf5bSMark Brown 			tm->tm_year += time1[3] & WM8350_RTC_YUNITS_MASK;
70077eaf5bSMark Brown 
71077eaf5bSMark Brown 			tm->tm_yday = rtc_year_days(tm->tm_mday, tm->tm_mon,
72077eaf5bSMark Brown 						    tm->tm_year);
73077eaf5bSMark Brown 			tm->tm_year -= 1900;
74077eaf5bSMark Brown 
75077eaf5bSMark Brown 			dev_dbg(dev, "Read (%d left): %04x %04x %04x %04x\n",
76077eaf5bSMark Brown 				retries,
77077eaf5bSMark Brown 				time1[0], time1[1], time1[2], time1[3]);
78077eaf5bSMark Brown 
79077eaf5bSMark Brown 			return 0;
80077eaf5bSMark Brown 		}
81077eaf5bSMark Brown 	} while (retries--);
82077eaf5bSMark Brown 
83077eaf5bSMark Brown 	dev_err(dev, "timed out reading RTC time\n");
84077eaf5bSMark Brown 	return -EIO;
85077eaf5bSMark Brown }
86077eaf5bSMark Brown 
87077eaf5bSMark Brown /*
88077eaf5bSMark Brown  * Set current time and date in RTC
89077eaf5bSMark Brown  */
wm8350_rtc_settime(struct device * dev,struct rtc_time * tm)90077eaf5bSMark Brown static int wm8350_rtc_settime(struct device *dev, struct rtc_time *tm)
91077eaf5bSMark Brown {
92077eaf5bSMark Brown 	struct wm8350 *wm8350 = dev_get_drvdata(dev);
93077eaf5bSMark Brown 	u16 time[4];
94077eaf5bSMark Brown 	u16 rtc_ctrl;
95077eaf5bSMark Brown 	int ret, retries = WM8350_SET_TIME_RETRIES;
96077eaf5bSMark Brown 
97077eaf5bSMark Brown 	time[0] = tm->tm_sec;
98077eaf5bSMark Brown 	time[0] |= tm->tm_min << WM8350_RTC_MINS_SHIFT;
99077eaf5bSMark Brown 	time[1] = tm->tm_hour;
100077eaf5bSMark Brown 	time[1] |= (tm->tm_wday + 1) << WM8350_RTC_DAY_SHIFT;
101077eaf5bSMark Brown 	time[2] = tm->tm_mday;
102077eaf5bSMark Brown 	time[2] |= (tm->tm_mon + 1) << WM8350_RTC_MTH_SHIFT;
103077eaf5bSMark Brown 	time[3] = ((tm->tm_year + 1900) / 100) << WM8350_RTC_YHUNDREDS_SHIFT;
104077eaf5bSMark Brown 	time[3] |= (tm->tm_year + 1900) % 100;
105077eaf5bSMark Brown 
106077eaf5bSMark Brown 	dev_dbg(dev, "Setting: %04x %04x %04x %04x\n",
107077eaf5bSMark Brown 		time[0], time[1], time[2], time[3]);
108077eaf5bSMark Brown 
109077eaf5bSMark Brown 	/* Set RTC_SET to stop the clock */
110077eaf5bSMark Brown 	ret = wm8350_set_bits(wm8350, WM8350_RTC_TIME_CONTROL, WM8350_RTC_SET);
111077eaf5bSMark Brown 	if (ret < 0)
112077eaf5bSMark Brown 		return ret;
113077eaf5bSMark Brown 
114077eaf5bSMark Brown 	/* Wait until confirmation of stopping */
115077eaf5bSMark Brown 	do {
116077eaf5bSMark Brown 		rtc_ctrl = wm8350_reg_read(wm8350, WM8350_RTC_TIME_CONTROL);
117077eaf5bSMark Brown 		schedule_timeout_uninterruptible(msecs_to_jiffies(1));
11862da659aSRoel Kluin 	} while (--retries && !(rtc_ctrl & WM8350_RTC_STS));
119077eaf5bSMark Brown 
120077eaf5bSMark Brown 	if (!retries) {
121077eaf5bSMark Brown 		dev_err(dev, "timed out on set confirmation\n");
122077eaf5bSMark Brown 		return -EIO;
123077eaf5bSMark Brown 	}
124077eaf5bSMark Brown 
125077eaf5bSMark Brown 	/* Write time to RTC */
126077eaf5bSMark Brown 	ret = wm8350_block_write(wm8350, WM8350_RTC_SECONDS_MINUTES, 4, time);
127077eaf5bSMark Brown 	if (ret < 0)
128077eaf5bSMark Brown 		return ret;
129077eaf5bSMark Brown 
130077eaf5bSMark Brown 	/* Clear RTC_SET to start the clock */
131077eaf5bSMark Brown 	ret = wm8350_clear_bits(wm8350, WM8350_RTC_TIME_CONTROL,
132077eaf5bSMark Brown 				WM8350_RTC_SET);
133077eaf5bSMark Brown 	return ret;
134077eaf5bSMark Brown }
135077eaf5bSMark Brown 
136077eaf5bSMark Brown /*
137077eaf5bSMark Brown  * Read alarm time and date in RTC
138077eaf5bSMark Brown  */
wm8350_rtc_readalarm(struct device * dev,struct rtc_wkalrm * alrm)139077eaf5bSMark Brown static int wm8350_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm)
140077eaf5bSMark Brown {
141077eaf5bSMark Brown 	struct wm8350 *wm8350 = dev_get_drvdata(dev);
142077eaf5bSMark Brown 	struct rtc_time *tm = &alrm->time;
143077eaf5bSMark Brown 	u16 time[4];
144077eaf5bSMark Brown 	int ret;
145077eaf5bSMark Brown 
146077eaf5bSMark Brown 	ret = wm8350_block_read(wm8350, WM8350_ALARM_SECONDS_MINUTES, 4, time);
147077eaf5bSMark Brown 	if (ret < 0)
148077eaf5bSMark Brown 		return ret;
149077eaf5bSMark Brown 
150077eaf5bSMark Brown 	tm->tm_sec = time[0] & WM8350_RTC_ALMSECS_MASK;
151077eaf5bSMark Brown 	if (tm->tm_sec == WM8350_RTC_ALMSECS_MASK)
152077eaf5bSMark Brown 		tm->tm_sec = -1;
153077eaf5bSMark Brown 
154077eaf5bSMark Brown 	tm->tm_min = time[0] & WM8350_RTC_ALMMINS_MASK;
155077eaf5bSMark Brown 	if (tm->tm_min == WM8350_RTC_ALMMINS_MASK)
156077eaf5bSMark Brown 		tm->tm_min = -1;
157077eaf5bSMark Brown 	else
158077eaf5bSMark Brown 		tm->tm_min >>= WM8350_RTC_ALMMINS_SHIFT;
159077eaf5bSMark Brown 
160077eaf5bSMark Brown 	tm->tm_hour = time[1] & WM8350_RTC_ALMHRS_MASK;
161077eaf5bSMark Brown 	if (tm->tm_hour == WM8350_RTC_ALMHRS_MASK)
162077eaf5bSMark Brown 		tm->tm_hour = -1;
163077eaf5bSMark Brown 
164077eaf5bSMark Brown 	tm->tm_wday = ((time[1] >> WM8350_RTC_ALMDAY_SHIFT) & 0x7) - 1;
165077eaf5bSMark Brown 	if (tm->tm_wday > 7)
166077eaf5bSMark Brown 		tm->tm_wday = -1;
167077eaf5bSMark Brown 
168077eaf5bSMark Brown 	tm->tm_mon = time[2] & WM8350_RTC_ALMMTH_MASK;
169077eaf5bSMark Brown 	if (tm->tm_mon == WM8350_RTC_ALMMTH_MASK)
170077eaf5bSMark Brown 		tm->tm_mon = -1;
171077eaf5bSMark Brown 	else
172077eaf5bSMark Brown 		tm->tm_mon = (tm->tm_mon >> WM8350_RTC_ALMMTH_SHIFT) - 1;
173077eaf5bSMark Brown 
174077eaf5bSMark Brown 	tm->tm_mday = (time[2] & WM8350_RTC_ALMDATE_MASK);
175077eaf5bSMark Brown 	if (tm->tm_mday == WM8350_RTC_ALMDATE_MASK)
176077eaf5bSMark Brown 		tm->tm_mday = -1;
177077eaf5bSMark Brown 
178077eaf5bSMark Brown 	tm->tm_year = -1;
179077eaf5bSMark Brown 
180077eaf5bSMark Brown 	alrm->enabled = !(time[3] & WM8350_RTC_ALMSTS);
181077eaf5bSMark Brown 
182077eaf5bSMark Brown 	return 0;
183077eaf5bSMark Brown }
184077eaf5bSMark Brown 
wm8350_rtc_stop_alarm(struct wm8350 * wm8350)185077eaf5bSMark Brown static int wm8350_rtc_stop_alarm(struct wm8350 *wm8350)
186077eaf5bSMark Brown {
187077eaf5bSMark Brown 	int retries = WM8350_SET_ALM_RETRIES;
188077eaf5bSMark Brown 	u16 rtc_ctrl;
189077eaf5bSMark Brown 	int ret;
190077eaf5bSMark Brown 
191077eaf5bSMark Brown 	/* Set RTC_SET to stop the clock */
192077eaf5bSMark Brown 	ret = wm8350_set_bits(wm8350, WM8350_RTC_TIME_CONTROL,
193077eaf5bSMark Brown 			      WM8350_RTC_ALMSET);
194077eaf5bSMark Brown 	if (ret < 0)
195077eaf5bSMark Brown 		return ret;
196077eaf5bSMark Brown 
197077eaf5bSMark Brown 	/* Wait until confirmation of stopping */
198077eaf5bSMark Brown 	do {
199077eaf5bSMark Brown 		rtc_ctrl = wm8350_reg_read(wm8350, WM8350_RTC_TIME_CONTROL);
200077eaf5bSMark Brown 		schedule_timeout_uninterruptible(msecs_to_jiffies(1));
201077eaf5bSMark Brown 	} while (retries-- && !(rtc_ctrl & WM8350_RTC_ALMSTS));
202077eaf5bSMark Brown 
203077eaf5bSMark Brown 	if (!(rtc_ctrl & WM8350_RTC_ALMSTS))
204077eaf5bSMark Brown 		return -ETIMEDOUT;
205077eaf5bSMark Brown 
206077eaf5bSMark Brown 	return 0;
207077eaf5bSMark Brown }
208077eaf5bSMark Brown 
wm8350_rtc_start_alarm(struct wm8350 * wm8350)209077eaf5bSMark Brown static int wm8350_rtc_start_alarm(struct wm8350 *wm8350)
210077eaf5bSMark Brown {
211077eaf5bSMark Brown 	int ret;
212077eaf5bSMark Brown 	int retries = WM8350_SET_ALM_RETRIES;
213077eaf5bSMark Brown 	u16 rtc_ctrl;
214077eaf5bSMark Brown 
215077eaf5bSMark Brown 	ret = wm8350_clear_bits(wm8350, WM8350_RTC_TIME_CONTROL,
216077eaf5bSMark Brown 				WM8350_RTC_ALMSET);
217077eaf5bSMark Brown 	if (ret < 0)
218077eaf5bSMark Brown 		return ret;
219077eaf5bSMark Brown 
220077eaf5bSMark Brown 	/* Wait until confirmation */
221077eaf5bSMark Brown 	do {
222077eaf5bSMark Brown 		rtc_ctrl = wm8350_reg_read(wm8350, WM8350_RTC_TIME_CONTROL);
223077eaf5bSMark Brown 		schedule_timeout_uninterruptible(msecs_to_jiffies(1));
224077eaf5bSMark Brown 	} while (retries-- && rtc_ctrl & WM8350_RTC_ALMSTS);
225077eaf5bSMark Brown 
226077eaf5bSMark Brown 	if (rtc_ctrl & WM8350_RTC_ALMSTS)
227077eaf5bSMark Brown 		return -ETIMEDOUT;
228077eaf5bSMark Brown 
229077eaf5bSMark Brown 	return 0;
230077eaf5bSMark Brown }
231077eaf5bSMark Brown 
wm8350_rtc_alarm_irq_enable(struct device * dev,unsigned int enabled)23247367a3bSMark Brown static int wm8350_rtc_alarm_irq_enable(struct device *dev,
23347367a3bSMark Brown 				       unsigned int enabled)
23447367a3bSMark Brown {
23547367a3bSMark Brown 	struct wm8350 *wm8350 = dev_get_drvdata(dev);
23647367a3bSMark Brown 
23747367a3bSMark Brown 	if (enabled)
23847367a3bSMark Brown 		return wm8350_rtc_start_alarm(wm8350);
23947367a3bSMark Brown 	else
24047367a3bSMark Brown 		return wm8350_rtc_stop_alarm(wm8350);
24147367a3bSMark Brown }
24247367a3bSMark Brown 
wm8350_rtc_setalarm(struct device * dev,struct rtc_wkalrm * alrm)243077eaf5bSMark Brown static int wm8350_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
244077eaf5bSMark Brown {
245077eaf5bSMark Brown 	struct wm8350 *wm8350 = dev_get_drvdata(dev);
246077eaf5bSMark Brown 	struct rtc_time *tm = &alrm->time;
247077eaf5bSMark Brown 	u16 time[3];
248077eaf5bSMark Brown 	int ret;
249077eaf5bSMark Brown 
250077eaf5bSMark Brown 	memset(time, 0, sizeof(time));
251077eaf5bSMark Brown 
252077eaf5bSMark Brown 	if (tm->tm_sec != -1)
253077eaf5bSMark Brown 		time[0] |= tm->tm_sec;
254077eaf5bSMark Brown 	else
255077eaf5bSMark Brown 		time[0] |= WM8350_RTC_ALMSECS_MASK;
256077eaf5bSMark Brown 
257077eaf5bSMark Brown 	if (tm->tm_min != -1)
258077eaf5bSMark Brown 		time[0] |= tm->tm_min << WM8350_RTC_ALMMINS_SHIFT;
259077eaf5bSMark Brown 	else
260077eaf5bSMark Brown 		time[0] |= WM8350_RTC_ALMMINS_MASK;
261077eaf5bSMark Brown 
262077eaf5bSMark Brown 	if (tm->tm_hour != -1)
263077eaf5bSMark Brown 		time[1] |= tm->tm_hour;
264077eaf5bSMark Brown 	else
265077eaf5bSMark Brown 		time[1] |= WM8350_RTC_ALMHRS_MASK;
266077eaf5bSMark Brown 
267077eaf5bSMark Brown 	if (tm->tm_wday != -1)
268077eaf5bSMark Brown 		time[1] |= (tm->tm_wday + 1) << WM8350_RTC_ALMDAY_SHIFT;
269077eaf5bSMark Brown 	else
270077eaf5bSMark Brown 		time[1] |= WM8350_RTC_ALMDAY_MASK;
271077eaf5bSMark Brown 
272077eaf5bSMark Brown 	if (tm->tm_mday != -1)
273077eaf5bSMark Brown 		time[2] |= tm->tm_mday;
274077eaf5bSMark Brown 	else
275077eaf5bSMark Brown 		time[2] |= WM8350_RTC_ALMDATE_MASK;
276077eaf5bSMark Brown 
277077eaf5bSMark Brown 	if (tm->tm_mon != -1)
278077eaf5bSMark Brown 		time[2] |= (tm->tm_mon + 1) << WM8350_RTC_ALMMTH_SHIFT;
279077eaf5bSMark Brown 	else
280077eaf5bSMark Brown 		time[2] |= WM8350_RTC_ALMMTH_MASK;
281077eaf5bSMark Brown 
282077eaf5bSMark Brown 	ret = wm8350_rtc_stop_alarm(wm8350);
283077eaf5bSMark Brown 	if (ret < 0)
284077eaf5bSMark Brown 		return ret;
285077eaf5bSMark Brown 
286077eaf5bSMark Brown 	/* Write time to RTC */
287077eaf5bSMark Brown 	ret = wm8350_block_write(wm8350, WM8350_ALARM_SECONDS_MINUTES,
288077eaf5bSMark Brown 				 3, time);
289077eaf5bSMark Brown 	if (ret < 0)
290077eaf5bSMark Brown 		return ret;
291077eaf5bSMark Brown 
292077eaf5bSMark Brown 	if (alrm->enabled)
293077eaf5bSMark Brown 		ret = wm8350_rtc_start_alarm(wm8350);
294077eaf5bSMark Brown 
295077eaf5bSMark Brown 	return ret;
296077eaf5bSMark Brown }
297077eaf5bSMark Brown 
wm8350_rtc_alarm_handler(int irq,void * data)2985a65edbcSMark Brown static irqreturn_t wm8350_rtc_alarm_handler(int irq, void *data)
299077eaf5bSMark Brown {
3005a65edbcSMark Brown 	struct wm8350 *wm8350 = data;
301077eaf5bSMark Brown 	struct rtc_device *rtc = wm8350->rtc.rtc;
302077eaf5bSMark Brown 	int ret;
303077eaf5bSMark Brown 
304077eaf5bSMark Brown 	rtc_update_irq(rtc, 1, RTC_IRQF | RTC_AF);
305077eaf5bSMark Brown 
306077eaf5bSMark Brown 	/* Make it one shot */
307077eaf5bSMark Brown 	ret = wm8350_set_bits(wm8350, WM8350_RTC_TIME_CONTROL,
308077eaf5bSMark Brown 			      WM8350_RTC_ALMSET);
309077eaf5bSMark Brown 	if (ret != 0) {
310077eaf5bSMark Brown 		dev_err(&(wm8350->rtc.pdev->dev),
311077eaf5bSMark Brown 			"Failed to disable alarm: %d\n", ret);
312077eaf5bSMark Brown 	}
3135a65edbcSMark Brown 
3145a65edbcSMark Brown 	return IRQ_HANDLED;
315077eaf5bSMark Brown }
316077eaf5bSMark Brown 
wm8350_rtc_update_handler(int irq,void * data)3175a65edbcSMark Brown static irqreturn_t wm8350_rtc_update_handler(int irq, void *data)
318077eaf5bSMark Brown {
3195a65edbcSMark Brown 	struct wm8350 *wm8350 = data;
320077eaf5bSMark Brown 	struct rtc_device *rtc = wm8350->rtc.rtc;
321077eaf5bSMark Brown 
322077eaf5bSMark Brown 	rtc_update_irq(rtc, 1, RTC_IRQF | RTC_UF);
3235a65edbcSMark Brown 
3245a65edbcSMark Brown 	return IRQ_HANDLED;
325077eaf5bSMark Brown }
326077eaf5bSMark Brown 
327077eaf5bSMark Brown static const struct rtc_class_ops wm8350_rtc_ops = {
328077eaf5bSMark Brown 	.read_time = wm8350_rtc_readtime,
329077eaf5bSMark Brown 	.set_time = wm8350_rtc_settime,
330077eaf5bSMark Brown 	.read_alarm = wm8350_rtc_readalarm,
331077eaf5bSMark Brown 	.set_alarm = wm8350_rtc_setalarm,
33247367a3bSMark Brown 	.alarm_irq_enable = wm8350_rtc_alarm_irq_enable,
333077eaf5bSMark Brown };
334077eaf5bSMark Brown 
3357d17158cSJingoo Han #ifdef CONFIG_PM_SLEEP
wm8350_rtc_suspend(struct device * dev)3366f38b043SMark Brown static int wm8350_rtc_suspend(struct device *dev)
337077eaf5bSMark Brown {
338527bd754SKefeng Wang 	struct wm8350 *wm8350 = dev_get_drvdata(dev);
339077eaf5bSMark Brown 	int ret = 0;
340077eaf5bSMark Brown 	u16 reg;
341077eaf5bSMark Brown 
342077eaf5bSMark Brown 	reg = wm8350_reg_read(wm8350, WM8350_RTC_TIME_CONTROL);
343077eaf5bSMark Brown 
344077eaf5bSMark Brown 	if (device_may_wakeup(&wm8350->rtc.pdev->dev) &&
345077eaf5bSMark Brown 	    reg & WM8350_RTC_ALMSTS) {
346077eaf5bSMark Brown 		ret = wm8350_rtc_stop_alarm(wm8350);
347077eaf5bSMark Brown 		if (ret != 0)
348527bd754SKefeng Wang 			dev_err(dev, "Failed to stop RTC alarm: %d\n", ret);
349077eaf5bSMark Brown 	}
350077eaf5bSMark Brown 
351077eaf5bSMark Brown 	return ret;
352077eaf5bSMark Brown }
353077eaf5bSMark Brown 
wm8350_rtc_resume(struct device * dev)3546f38b043SMark Brown static int wm8350_rtc_resume(struct device *dev)
355077eaf5bSMark Brown {
356527bd754SKefeng Wang 	struct wm8350 *wm8350 = dev_get_drvdata(dev);
357077eaf5bSMark Brown 	int ret;
358077eaf5bSMark Brown 
359077eaf5bSMark Brown 	if (wm8350->rtc.alarm_enabled) {
360077eaf5bSMark Brown 		ret = wm8350_rtc_start_alarm(wm8350);
361077eaf5bSMark Brown 		if (ret != 0)
362527bd754SKefeng Wang 			dev_err(dev, "Failed to restart RTC alarm: %d\n", ret);
363077eaf5bSMark Brown 	}
364077eaf5bSMark Brown 
365077eaf5bSMark Brown 	return 0;
366077eaf5bSMark Brown }
367077eaf5bSMark Brown #endif
368077eaf5bSMark Brown 
wm8350_rtc_probe(struct platform_device * pdev)369077eaf5bSMark Brown static int wm8350_rtc_probe(struct platform_device *pdev)
370077eaf5bSMark Brown {
371077eaf5bSMark Brown 	struct wm8350 *wm8350 = platform_get_drvdata(pdev);
372077eaf5bSMark Brown 	struct wm8350_rtc *wm_rtc = &wm8350->rtc;
373077eaf5bSMark Brown 	int ret = 0;
374077eaf5bSMark Brown 	u16 timectl, power5;
375077eaf5bSMark Brown 
376077eaf5bSMark Brown 	timectl = wm8350_reg_read(wm8350, WM8350_RTC_TIME_CONTROL);
377077eaf5bSMark Brown 	if (timectl & WM8350_RTC_BCD) {
378077eaf5bSMark Brown 		dev_err(&pdev->dev, "RTC BCD mode not supported\n");
379077eaf5bSMark Brown 		return -EINVAL;
380077eaf5bSMark Brown 	}
381077eaf5bSMark Brown 	if (timectl & WM8350_RTC_12HR) {
382077eaf5bSMark Brown 		dev_err(&pdev->dev, "RTC 12 hour mode not supported\n");
383077eaf5bSMark Brown 		return -EINVAL;
384077eaf5bSMark Brown 	}
385077eaf5bSMark Brown 
386077eaf5bSMark Brown 	/* enable the RTC if it's not already enabled */
387077eaf5bSMark Brown 	power5 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_5);
388077eaf5bSMark Brown 	if (!(power5 &  WM8350_RTC_TICK_ENA)) {
389077eaf5bSMark Brown 		wm8350_reg_unlock(wm8350);
390077eaf5bSMark Brown 
391077eaf5bSMark Brown 		ret = wm8350_set_bits(wm8350, WM8350_POWER_MGMT_5,
392077eaf5bSMark Brown 				      WM8350_RTC_TICK_ENA);
393077eaf5bSMark Brown 		if (ret < 0) {
394077eaf5bSMark Brown 			dev_err(&pdev->dev, "failed to enable RTC: %d\n", ret);
395077eaf5bSMark Brown 			return ret;
396077eaf5bSMark Brown 		}
397077eaf5bSMark Brown 
398077eaf5bSMark Brown 		wm8350_reg_lock(wm8350);
399077eaf5bSMark Brown 	}
400077eaf5bSMark Brown 
401077eaf5bSMark Brown 	if (timectl & WM8350_RTC_STS) {
402077eaf5bSMark Brown 		int retries;
403077eaf5bSMark Brown 
404077eaf5bSMark Brown 		ret = wm8350_clear_bits(wm8350, WM8350_RTC_TIME_CONTROL,
405077eaf5bSMark Brown 					WM8350_RTC_SET);
406077eaf5bSMark Brown 		if (ret < 0) {
407077eaf5bSMark Brown 			dev_err(&pdev->dev, "failed to start: %d\n", ret);
408077eaf5bSMark Brown 			return ret;
409077eaf5bSMark Brown 		}
410077eaf5bSMark Brown 
411077eaf5bSMark Brown 		retries = WM8350_SET_TIME_RETRIES;
412077eaf5bSMark Brown 		do {
413077eaf5bSMark Brown 			timectl = wm8350_reg_read(wm8350,
414077eaf5bSMark Brown 						  WM8350_RTC_TIME_CONTROL);
41562da659aSRoel Kluin 		} while (timectl & WM8350_RTC_STS && --retries);
416077eaf5bSMark Brown 
417077eaf5bSMark Brown 		if (retries == 0) {
418077eaf5bSMark Brown 			dev_err(&pdev->dev, "failed to start: timeout\n");
419077eaf5bSMark Brown 			return -ENODEV;
420077eaf5bSMark Brown 		}
421077eaf5bSMark Brown 	}
422077eaf5bSMark Brown 
423077eaf5bSMark Brown 	device_init_wakeup(&pdev->dev, 1);
424077eaf5bSMark Brown 
425a5aa7776SJingoo Han 	wm_rtc->rtc = devm_rtc_device_register(&pdev->dev, "wm8350",
426077eaf5bSMark Brown 					&wm8350_rtc_ops, THIS_MODULE);
427*d844c64bSAlexandre Belloni 	if (IS_ERR(wm_rtc->rtc))
428*d844c64bSAlexandre Belloni 		return PTR_ERR(wm_rtc->rtc);
429077eaf5bSMark Brown 
43043f0269bSJiasheng Jiang 	ret = wm8350_register_irq(wm8350, WM8350_IRQ_RTC_SEC,
4315a65edbcSMark Brown 			    wm8350_rtc_update_handler, 0,
4325a65edbcSMark Brown 			    "RTC Seconds", wm8350);
43343f0269bSJiasheng Jiang 	if (ret)
43443f0269bSJiasheng Jiang 		return ret;
43543f0269bSJiasheng Jiang 
4366a612746SMark Brown 	wm8350_mask_irq(wm8350, WM8350_IRQ_RTC_SEC);
437077eaf5bSMark Brown 
43843f0269bSJiasheng Jiang 	ret = wm8350_register_irq(wm8350, WM8350_IRQ_RTC_ALM,
4395a65edbcSMark Brown 			    wm8350_rtc_alarm_handler, 0,
4405a65edbcSMark Brown 			    "RTC Alarm", wm8350);
44143f0269bSJiasheng Jiang 	if (ret) {
44243f0269bSJiasheng Jiang 		wm8350_free_irq(wm8350, WM8350_IRQ_RTC_SEC, wm8350);
44343f0269bSJiasheng Jiang 		return ret;
44443f0269bSJiasheng Jiang 	}
445077eaf5bSMark Brown 
446077eaf5bSMark Brown 	return 0;
447077eaf5bSMark Brown }
448077eaf5bSMark Brown 
wm8350_rtc_remove(struct platform_device * pdev)449631aa2d9SUwe Kleine-König static void wm8350_rtc_remove(struct platform_device *pdev)
450077eaf5bSMark Brown {
451077eaf5bSMark Brown 	struct wm8350 *wm8350 = platform_get_drvdata(pdev);
452077eaf5bSMark Brown 
453f99344fcSMark Brown 	wm8350_free_irq(wm8350, WM8350_IRQ_RTC_SEC, wm8350);
454f99344fcSMark Brown 	wm8350_free_irq(wm8350, WM8350_IRQ_RTC_ALM, wm8350);
455077eaf5bSMark Brown }
456077eaf5bSMark Brown 
4577d17158cSJingoo Han static SIMPLE_DEV_PM_OPS(wm8350_rtc_pm_ops, wm8350_rtc_suspend,
4587d17158cSJingoo Han 			wm8350_rtc_resume);
4596f38b043SMark Brown 
460077eaf5bSMark Brown static struct platform_driver wm8350_rtc_driver = {
461077eaf5bSMark Brown 	.probe = wm8350_rtc_probe,
462631aa2d9SUwe Kleine-König 	.remove_new = wm8350_rtc_remove,
463077eaf5bSMark Brown 	.driver = {
464077eaf5bSMark Brown 		.name = "wm8350-rtc",
4656f38b043SMark Brown 		.pm = &wm8350_rtc_pm_ops,
466077eaf5bSMark Brown 	},
467077eaf5bSMark Brown };
468077eaf5bSMark Brown 
4690c4eae66SAxel Lin module_platform_driver(wm8350_rtc_driver);
470077eaf5bSMark Brown 
471077eaf5bSMark Brown MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
472077eaf5bSMark Brown MODULE_DESCRIPTION("RTC driver for the WM8350");
473077eaf5bSMark Brown MODULE_LICENSE("GPL");
474077eaf5bSMark Brown MODULE_ALIAS("platform:wm8350-rtc");
475