Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
1add6781 | 2 | /* drivers/rtc/rtc-s3c.c |
e48add8c AD |
3 | * |
4 | * Copyright (c) 2010 Samsung Electronics Co., Ltd. | |
5 | * http://www.samsung.com/ | |
1add6781 BD |
6 | * |
7 | * Copyright (c) 2004,2006 Simtec Electronics | |
8 | * Ben Dooks, <ben@simtec.co.uk> | |
9 | * http://armlinux.simtec.co.uk/ | |
10 | * | |
1add6781 BD |
11 | * S3C2410/S3C2440/S3C24XX Internal RTC Driver |
12 | */ | |
13 | ||
14 | #include <linux/module.h> | |
15 | #include <linux/fs.h> | |
16 | #include <linux/string.h> | |
17 | #include <linux/init.h> | |
18 | #include <linux/platform_device.h> | |
19 | #include <linux/interrupt.h> | |
20 | #include <linux/rtc.h> | |
21 | #include <linux/bcd.h> | |
22 | #include <linux/clk.h> | |
9974b6ea | 23 | #include <linux/log2.h> |
5a0e3ad6 | 24 | #include <linux/slab.h> |
39ce4084 | 25 | #include <linux/of.h> |
dbd9acbe SK |
26 | #include <linux/uaccess.h> |
27 | #include <linux/io.h> | |
1add6781 | 28 | |
1add6781 | 29 | #include <asm/irq.h> |
b9d7c5d3 | 30 | #include "rtc-s3c.h" |
1add6781 | 31 | |
19be09f5 CC |
32 | struct s3c_rtc { |
33 | struct device *dev; | |
34 | struct rtc_device *rtc; | |
35 | ||
36 | void __iomem *base; | |
37 | struct clk *rtc_clk; | |
df9e26d0 | 38 | struct clk *rtc_src_clk; |
5a5b614b | 39 | bool alarm_enabled; |
19be09f5 | 40 | |
6b72086d | 41 | const struct s3c_rtc_data *data; |
1add6781 | 42 | |
19be09f5 | 43 | int irq_alarm; |
5a5b614b | 44 | spinlock_t alarm_lock; |
1add6781 | 45 | |
19be09f5 CC |
46 | bool wake_en; |
47 | }; | |
48 | ||
ae05c950 | 49 | struct s3c_rtc_data { |
df9e26d0 | 50 | bool needs_src_clk; |
ae05c950 CC |
51 | |
52 | void (*irq_handler) (struct s3c_rtc *info, int mask); | |
ae05c950 CC |
53 | void (*enable) (struct s3c_rtc *info); |
54 | void (*disable) (struct s3c_rtc *info); | |
55 | }; | |
56 | ||
498bcf31 | 57 | static int s3c_rtc_enable_clk(struct s3c_rtc *info) |
88cee8fd | 58 | { |
5a5b614b | 59 | int ret; |
88cee8fd | 60 | |
5a5b614b MS |
61 | ret = clk_enable(info->rtc_clk); |
62 | if (ret) | |
63 | return ret; | |
498bcf31 | 64 | |
5a5b614b MS |
65 | if (info->data->needs_src_clk) { |
66 | ret = clk_enable(info->rtc_src_clk); | |
67 | if (ret) { | |
68 | clk_disable(info->rtc_clk); | |
69 | return ret; | |
498bcf31 | 70 | } |
1fb1c35f | 71 | } |
5a5b614b | 72 | return 0; |
24e14554 CC |
73 | } |
74 | ||
75 | static void s3c_rtc_disable_clk(struct s3c_rtc *info) | |
76 | { | |
5a5b614b MS |
77 | if (info->data->needs_src_clk) |
78 | clk_disable(info->rtc_src_clk); | |
79 | clk_disable(info->rtc_clk); | |
88cee8fd DK |
80 | } |
81 | ||
ce9af893 | 82 | /* IRQ Handler */ |
ae05c950 | 83 | static irqreturn_t s3c_rtc_alarmirq(int irq, void *id) |
1add6781 | 84 | { |
19be09f5 | 85 | struct s3c_rtc *info = (struct s3c_rtc *)id; |
1add6781 | 86 | |
ae05c950 CC |
87 | if (info->data->irq_handler) |
88 | info->data->irq_handler(info, S3C2410_INTP_ALM); | |
2f3478f6 | 89 | |
1add6781 BD |
90 | return IRQ_HANDLED; |
91 | } | |
92 | ||
93 | /* Update control registers */ | |
2ec38a03 | 94 | static int s3c_rtc_setaie(struct device *dev, unsigned int enabled) |
1add6781 | 95 | { |
19be09f5 | 96 | struct s3c_rtc *info = dev_get_drvdata(dev); |
5a5b614b | 97 | unsigned long flags; |
1add6781 | 98 | unsigned int tmp; |
498bcf31 | 99 | int ret; |
1add6781 | 100 | |
19be09f5 | 101 | dev_dbg(info->dev, "%s: aie=%d\n", __func__, enabled); |
1add6781 | 102 | |
498bcf31 KK |
103 | ret = s3c_rtc_enable_clk(info); |
104 | if (ret) | |
105 | return ret; | |
24e14554 | 106 | |
19be09f5 | 107 | tmp = readb(info->base + S3C2410_RTCALM) & ~S3C2410_RTCALM_ALMEN; |
1add6781 | 108 | |
2ec38a03 | 109 | if (enabled) |
1add6781 BD |
110 | tmp |= S3C2410_RTCALM_ALMEN; |
111 | ||
19be09f5 | 112 | writeb(tmp, info->base + S3C2410_RTCALM); |
2ec38a03 | 113 | |
5a5b614b | 114 | spin_lock_irqsave(&info->alarm_lock, flags); |
88cee8fd | 115 | |
5a5b614b | 116 | if (info->alarm_enabled && !enabled) |
1fb1c35f | 117 | s3c_rtc_disable_clk(info); |
5a5b614b MS |
118 | else if (!info->alarm_enabled && enabled) |
119 | ret = s3c_rtc_enable_clk(info); | |
1fb1c35f | 120 | |
5a5b614b MS |
121 | info->alarm_enabled = enabled; |
122 | spin_unlock_irqrestore(&info->alarm_lock, flags); | |
123 | ||
124 | s3c_rtc_disable_clk(info); | |
125 | ||
126 | return ret; | |
1add6781 BD |
127 | } |
128 | ||
e4a1444e SP |
129 | /* Read time from RTC and convert it from BCD */ |
130 | static int s3c_rtc_read_time(struct s3c_rtc *info, struct rtc_time *tm) | |
1add6781 BD |
131 | { |
132 | unsigned int have_retried = 0; | |
498bcf31 | 133 | int ret; |
1add6781 | 134 | |
498bcf31 KK |
135 | ret = s3c_rtc_enable_clk(info); |
136 | if (ret) | |
137 | return ret; | |
df9e26d0 | 138 | |
fc1afe60 | 139 | retry_get_time: |
e4a1444e SP |
140 | tm->tm_min = readb(info->base + S3C2410_RTCMIN); |
141 | tm->tm_hour = readb(info->base + S3C2410_RTCHOUR); | |
142 | tm->tm_mday = readb(info->base + S3C2410_RTCDATE); | |
143 | tm->tm_mon = readb(info->base + S3C2410_RTCMON); | |
144 | tm->tm_year = readb(info->base + S3C2410_RTCYEAR); | |
145 | tm->tm_sec = readb(info->base + S3C2410_RTCSEC); | |
146 | ||
147 | /* | |
148 | * The only way to work out whether the system was mid-update | |
1add6781 BD |
149 | * when we read it is to check the second counter, and if it |
150 | * is zero, then we re-try the entire read | |
151 | */ | |
e4a1444e | 152 | if (tm->tm_sec == 0 && !have_retried) { |
1add6781 BD |
153 | have_retried = 1; |
154 | goto retry_get_time; | |
155 | } | |
156 | ||
24e14554 CC |
157 | s3c_rtc_disable_clk(info); |
158 | ||
e4a1444e SP |
159 | tm->tm_sec = bcd2bin(tm->tm_sec); |
160 | tm->tm_min = bcd2bin(tm->tm_min); | |
161 | tm->tm_hour = bcd2bin(tm->tm_hour); | |
162 | tm->tm_mday = bcd2bin(tm->tm_mday); | |
163 | tm->tm_mon = bcd2bin(tm->tm_mon); | |
164 | tm->tm_year = bcd2bin(tm->tm_year); | |
1add6781 | 165 | |
22652ba7 | 166 | return 0; |
1add6781 BD |
167 | } |
168 | ||
e4a1444e SP |
169 | /* Convert time to BCD and write it to RTC */ |
170 | static int s3c_rtc_write_time(struct s3c_rtc *info, const struct rtc_time *tm) | |
1add6781 | 171 | { |
498bcf31 | 172 | int ret; |
9a654518 | 173 | |
498bcf31 KK |
174 | ret = s3c_rtc_enable_clk(info); |
175 | if (ret) | |
176 | return ret; | |
19be09f5 CC |
177 | |
178 | writeb(bin2bcd(tm->tm_sec), info->base + S3C2410_RTCSEC); | |
179 | writeb(bin2bcd(tm->tm_min), info->base + S3C2410_RTCMIN); | |
180 | writeb(bin2bcd(tm->tm_hour), info->base + S3C2410_RTCHOUR); | |
181 | writeb(bin2bcd(tm->tm_mday), info->base + S3C2410_RTCDATE); | |
e4a1444e SP |
182 | writeb(bin2bcd(tm->tm_mon), info->base + S3C2410_RTCMON); |
183 | writeb(bin2bcd(tm->tm_year), info->base + S3C2410_RTCYEAR); | |
19be09f5 | 184 | |
24e14554 | 185 | s3c_rtc_disable_clk(info); |
1add6781 BD |
186 | |
187 | return 0; | |
188 | } | |
189 | ||
e4a1444e SP |
190 | static int s3c_rtc_gettime(struct device *dev, struct rtc_time *tm) |
191 | { | |
192 | struct s3c_rtc *info = dev_get_drvdata(dev); | |
193 | int ret; | |
194 | ||
195 | ret = s3c_rtc_read_time(info, tm); | |
196 | if (ret) | |
197 | return ret; | |
198 | ||
199 | /* Convert internal representation to actual date/time */ | |
200 | tm->tm_year += 100; | |
201 | tm->tm_mon -= 1; | |
202 | ||
203 | dev_dbg(dev, "read time %ptR\n", tm); | |
204 | return 0; | |
205 | } | |
206 | ||
207 | static int s3c_rtc_settime(struct device *dev, struct rtc_time *tm) | |
208 | { | |
209 | struct s3c_rtc *info = dev_get_drvdata(dev); | |
210 | struct rtc_time rtc_tm = *tm; | |
211 | ||
212 | dev_dbg(dev, "set time %ptR\n", tm); | |
213 | ||
214 | /* | |
215 | * Convert actual date/time to internal representation. | |
216 | * We get around Y2K by simply not supporting it. | |
217 | */ | |
218 | rtc_tm.tm_year -= 100; | |
219 | rtc_tm.tm_mon += 1; | |
220 | ||
e4a1444e SP |
221 | return s3c_rtc_write_time(info, &rtc_tm); |
222 | } | |
223 | ||
1add6781 BD |
224 | static int s3c_rtc_getalarm(struct device *dev, struct rtc_wkalrm *alrm) |
225 | { | |
19be09f5 | 226 | struct s3c_rtc *info = dev_get_drvdata(dev); |
1add6781 BD |
227 | struct rtc_time *alm_tm = &alrm->time; |
228 | unsigned int alm_en; | |
498bcf31 | 229 | int ret; |
1add6781 | 230 | |
498bcf31 KK |
231 | ret = s3c_rtc_enable_clk(info); |
232 | if (ret) | |
233 | return ret; | |
df9e26d0 | 234 | |
19be09f5 CC |
235 | alm_tm->tm_sec = readb(info->base + S3C2410_ALMSEC); |
236 | alm_tm->tm_min = readb(info->base + S3C2410_ALMMIN); | |
237 | alm_tm->tm_hour = readb(info->base + S3C2410_ALMHOUR); | |
238 | alm_tm->tm_mon = readb(info->base + S3C2410_ALMMON); | |
239 | alm_tm->tm_mday = readb(info->base + S3C2410_ALMDATE); | |
240 | alm_tm->tm_year = readb(info->base + S3C2410_ALMYEAR); | |
1add6781 | 241 | |
19be09f5 | 242 | alm_en = readb(info->base + S3C2410_RTCALM); |
1add6781 | 243 | |
24e14554 CC |
244 | s3c_rtc_disable_clk(info); |
245 | ||
a2db8dfc DB |
246 | alrm->enabled = (alm_en & S3C2410_RTCALM_ALMEN) ? 1 : 0; |
247 | ||
9a1bacf4 | 248 | dev_dbg(dev, "read alarm %d, %ptR\n", alm_en, alm_tm); |
1add6781 | 249 | |
1add6781 | 250 | /* decode the alarm enable field */ |
1add6781 | 251 | if (alm_en & S3C2410_RTCALM_SECEN) |
fe20ba70 | 252 | alm_tm->tm_sec = bcd2bin(alm_tm->tm_sec); |
1add6781 BD |
253 | |
254 | if (alm_en & S3C2410_RTCALM_MINEN) | |
fe20ba70 | 255 | alm_tm->tm_min = bcd2bin(alm_tm->tm_min); |
1add6781 BD |
256 | |
257 | if (alm_en & S3C2410_RTCALM_HOUREN) | |
fe20ba70 | 258 | alm_tm->tm_hour = bcd2bin(alm_tm->tm_hour); |
1add6781 BD |
259 | |
260 | if (alm_en & S3C2410_RTCALM_DAYEN) | |
fe20ba70 | 261 | alm_tm->tm_mday = bcd2bin(alm_tm->tm_mday); |
1add6781 BD |
262 | |
263 | if (alm_en & S3C2410_RTCALM_MONEN) { | |
fe20ba70 | 264 | alm_tm->tm_mon = bcd2bin(alm_tm->tm_mon); |
1add6781 | 265 | alm_tm->tm_mon -= 1; |
1add6781 BD |
266 | } |
267 | ||
268 | if (alm_en & S3C2410_RTCALM_YEAREN) | |
fe20ba70 | 269 | alm_tm->tm_year = bcd2bin(alm_tm->tm_year); |
1add6781 BD |
270 | |
271 | return 0; | |
272 | } | |
273 | ||
274 | static int s3c_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm) | |
275 | { | |
19be09f5 | 276 | struct s3c_rtc *info = dev_get_drvdata(dev); |
1add6781 BD |
277 | struct rtc_time *tm = &alrm->time; |
278 | unsigned int alrm_en; | |
498bcf31 | 279 | int ret; |
1add6781 | 280 | |
9a1bacf4 | 281 | dev_dbg(dev, "s3c_rtc_setalarm: %d, %ptR\n", alrm->enabled, tm); |
1add6781 | 282 | |
498bcf31 KK |
283 | ret = s3c_rtc_enable_clk(info); |
284 | if (ret) | |
285 | return ret; | |
24e14554 | 286 | |
19be09f5 CC |
287 | alrm_en = readb(info->base + S3C2410_RTCALM) & S3C2410_RTCALM_ALMEN; |
288 | writeb(0x00, info->base + S3C2410_RTCALM); | |
1add6781 BD |
289 | |
290 | if (tm->tm_sec < 60 && tm->tm_sec >= 0) { | |
291 | alrm_en |= S3C2410_RTCALM_SECEN; | |
19be09f5 | 292 | writeb(bin2bcd(tm->tm_sec), info->base + S3C2410_ALMSEC); |
1add6781 BD |
293 | } |
294 | ||
295 | if (tm->tm_min < 60 && tm->tm_min >= 0) { | |
296 | alrm_en |= S3C2410_RTCALM_MINEN; | |
19be09f5 | 297 | writeb(bin2bcd(tm->tm_min), info->base + S3C2410_ALMMIN); |
1add6781 BD |
298 | } |
299 | ||
300 | if (tm->tm_hour < 24 && tm->tm_hour >= 0) { | |
301 | alrm_en |= S3C2410_RTCALM_HOUREN; | |
19be09f5 | 302 | writeb(bin2bcd(tm->tm_hour), info->base + S3C2410_ALMHOUR); |
1add6781 BD |
303 | } |
304 | ||
fb4ac3c1 KK |
305 | if (tm->tm_mon < 12 && tm->tm_mon >= 0) { |
306 | alrm_en |= S3C2410_RTCALM_MONEN; | |
307 | writeb(bin2bcd(tm->tm_mon + 1), info->base + S3C2410_ALMMON); | |
308 | } | |
309 | ||
310 | if (tm->tm_mday <= 31 && tm->tm_mday >= 1) { | |
311 | alrm_en |= S3C2410_RTCALM_DAYEN; | |
312 | writeb(bin2bcd(tm->tm_mday), info->base + S3C2410_ALMDATE); | |
313 | } | |
314 | ||
d4a48c2a | 315 | dev_dbg(dev, "setting S3C2410_RTCALM to %08x\n", alrm_en); |
1add6781 | 316 | |
19be09f5 | 317 | writeb(alrm_en, info->base + S3C2410_RTCALM); |
1add6781 | 318 | |
24e14554 | 319 | s3c_rtc_setaie(dev, alrm->enabled); |
19be09f5 | 320 | |
5a5b614b MS |
321 | s3c_rtc_disable_clk(info); |
322 | ||
1add6781 BD |
323 | return 0; |
324 | } | |
325 | ||
ff8371ac | 326 | static const struct rtc_class_ops s3c_rtcops = { |
1add6781 BD |
327 | .read_time = s3c_rtc_gettime, |
328 | .set_time = s3c_rtc_settime, | |
329 | .read_alarm = s3c_rtc_getalarm, | |
330 | .set_alarm = s3c_rtc_setalarm, | |
e6eb524e | 331 | .alarm_irq_enable = s3c_rtc_setaie, |
1add6781 BD |
332 | }; |
333 | ||
ae05c950 | 334 | static void s3c24xx_rtc_enable(struct s3c_rtc *info) |
1add6781 | 335 | { |
d67288da | 336 | unsigned int con, tmp; |
1add6781 | 337 | |
d67288da | 338 | con = readw(info->base + S3C2410_RTCCON); |
ae05c950 CC |
339 | /* re-enable the device, and check it is ok */ |
340 | if ((con & S3C2410_RTCCON_RTCEN) == 0) { | |
341 | dev_info(info->dev, "rtc disabled, re-enabling\n"); | |
1add6781 | 342 | |
ae05c950 | 343 | tmp = readw(info->base + S3C2410_RTCCON); |
fc1afe60 | 344 | writew(tmp | S3C2410_RTCCON_RTCEN, info->base + S3C2410_RTCCON); |
ae05c950 | 345 | } |
1add6781 | 346 | |
ae05c950 CC |
347 | if (con & S3C2410_RTCCON_CNTSEL) { |
348 | dev_info(info->dev, "removing RTCCON_CNTSEL\n"); | |
1add6781 | 349 | |
ae05c950 CC |
350 | tmp = readw(info->base + S3C2410_RTCCON); |
351 | writew(tmp & ~S3C2410_RTCCON_CNTSEL, | |
fc1afe60 | 352 | info->base + S3C2410_RTCCON); |
ae05c950 | 353 | } |
1add6781 | 354 | |
ae05c950 CC |
355 | if (con & S3C2410_RTCCON_CLKRST) { |
356 | dev_info(info->dev, "removing RTCCON_CLKRST\n"); | |
1add6781 | 357 | |
ae05c950 CC |
358 | tmp = readw(info->base + S3C2410_RTCCON); |
359 | writew(tmp & ~S3C2410_RTCCON_CLKRST, | |
fc1afe60 | 360 | info->base + S3C2410_RTCCON); |
1add6781 | 361 | } |
ae05c950 CC |
362 | } |
363 | ||
364 | static void s3c24xx_rtc_disable(struct s3c_rtc *info) | |
365 | { | |
366 | unsigned int con; | |
367 | ||
ae05c950 CC |
368 | con = readw(info->base + S3C2410_RTCCON); |
369 | con &= ~S3C2410_RTCCON_RTCEN; | |
370 | writew(con, info->base + S3C2410_RTCCON); | |
371 | ||
372 | con = readb(info->base + S3C2410_TICNT); | |
373 | con &= ~S3C2410_TICNT_ENABLE; | |
374 | writeb(con, info->base + S3C2410_TICNT); | |
ae05c950 CC |
375 | } |
376 | ||
377 | static void s3c6410_rtc_disable(struct s3c_rtc *info) | |
378 | { | |
379 | unsigned int con; | |
380 | ||
ae05c950 CC |
381 | con = readw(info->base + S3C2410_RTCCON); |
382 | con &= ~S3C64XX_RTCCON_TICEN; | |
383 | con &= ~S3C2410_RTCCON_RTCEN; | |
384 | writew(con, info->base + S3C2410_RTCCON); | |
1add6781 BD |
385 | } |
386 | ||
9e6a2ad1 | 387 | static void s3c_rtc_remove(struct platform_device *pdev) |
1add6781 | 388 | { |
19be09f5 CC |
389 | struct s3c_rtc *info = platform_get_drvdata(pdev); |
390 | ||
391 | s3c_rtc_setaie(info->dev, 0); | |
1add6781 | 392 | |
7f23a936 JS |
393 | if (info->data->needs_src_clk) |
394 | clk_unprepare(info->rtc_src_clk); | |
19be09f5 | 395 | clk_unprepare(info->rtc_clk); |
1add6781 BD |
396 | } |
397 | ||
5a167f45 | 398 | static int s3c_rtc_probe(struct platform_device *pdev) |
1add6781 | 399 | { |
19be09f5 | 400 | struct s3c_rtc *info = NULL; |
1add6781 BD |
401 | int ret; |
402 | ||
19be09f5 CC |
403 | info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); |
404 | if (!info) | |
405 | return -ENOMEM; | |
1add6781 | 406 | |
19be09f5 | 407 | info->dev = &pdev->dev; |
64704c92 | 408 | info->data = of_device_get_match_data(&pdev->dev); |
ae05c950 CC |
409 | if (!info->data) { |
410 | dev_err(&pdev->dev, "failed getting s3c_rtc_data\n"); | |
411 | return -EINVAL; | |
412 | } | |
5a5b614b | 413 | spin_lock_init(&info->alarm_lock); |
19be09f5 CC |
414 | |
415 | platform_set_drvdata(pdev, info); | |
416 | ||
417 | info->irq_alarm = platform_get_irq(pdev, 0); | |
faac9102 | 418 | if (info->irq_alarm < 0) |
19be09f5 | 419 | return info->irq_alarm; |
1add6781 | 420 | |
ce9af893 | 421 | dev_dbg(&pdev->dev, "s3c2410_rtc: alarm irq %d\n", info->irq_alarm); |
1add6781 BD |
422 | |
423 | /* get the memory region */ | |
09ef18bc | 424 | info->base = devm_platform_ioremap_resource(pdev, 0); |
19be09f5 CC |
425 | if (IS_ERR(info->base)) |
426 | return PTR_ERR(info->base); | |
1add6781 | 427 | |
19be09f5 | 428 | info->rtc_clk = devm_clk_get(&pdev->dev, "rtc"); |
eb633de6 YY |
429 | if (IS_ERR(info->rtc_clk)) |
430 | return dev_err_probe(&pdev->dev, PTR_ERR(info->rtc_clk), | |
431 | "failed to find rtc clock\n"); | |
9903f68a KK |
432 | ret = clk_prepare_enable(info->rtc_clk); |
433 | if (ret) | |
434 | return ret; | |
e48add8c | 435 | |
eaf3a659 MS |
436 | if (info->data->needs_src_clk) { |
437 | info->rtc_src_clk = devm_clk_get(&pdev->dev, "rtc_src"); | |
438 | if (IS_ERR(info->rtc_src_clk)) { | |
c52d270c KK |
439 | ret = dev_err_probe(&pdev->dev, PTR_ERR(info->rtc_src_clk), |
440 | "failed to find rtc source clock\n"); | |
8768e7b3 | 441 | goto err_src_clk; |
eaf3a659 | 442 | } |
9903f68a KK |
443 | ret = clk_prepare_enable(info->rtc_src_clk); |
444 | if (ret) | |
445 | goto err_src_clk; | |
df9e26d0 | 446 | } |
df9e26d0 | 447 | |
31b16d97 MS |
448 | /* disable RTC enable bits potentially set by the bootloader */ |
449 | if (info->data->disable) | |
450 | info->data->disable(info); | |
451 | ||
1add6781 | 452 | /* check to see if everything is setup correctly */ |
ae05c950 CC |
453 | if (info->data->enable) |
454 | info->data->enable(info); | |
1add6781 | 455 | |
d4a48c2a | 456 | dev_dbg(&pdev->dev, "s3c2410_rtc: RTCCON=%02x\n", |
fc1afe60 | 457 | readw(info->base + S3C2410_RTCCON)); |
1add6781 | 458 | |
51b7616e YK |
459 | device_init_wakeup(&pdev->dev, 1); |
460 | ||
dba28c37 | 461 | info->rtc = devm_rtc_allocate_device(&pdev->dev); |
19be09f5 | 462 | if (IS_ERR(info->rtc)) { |
19be09f5 | 463 | ret = PTR_ERR(info->rtc); |
1add6781 BD |
464 | goto err_nortc; |
465 | } | |
466 | ||
dba28c37 | 467 | info->rtc->ops = &s3c_rtcops; |
a5feda3b SP |
468 | info->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000; |
469 | info->rtc->range_max = RTC_TIMESTAMP_END_2099; | |
dba28c37 SP |
470 | |
471 | ret = devm_rtc_register_device(info->rtc); | |
472 | if (ret) | |
473 | goto err_nortc; | |
474 | ||
19be09f5 | 475 | ret = devm_request_irq(&pdev->dev, info->irq_alarm, s3c_rtc_alarmirq, |
fc1afe60 | 476 | 0, "s3c2410-rtc alarm", info); |
19be09f5 CC |
477 | if (ret) { |
478 | dev_err(&pdev->dev, "IRQ%d error %d\n", info->irq_alarm, ret); | |
479 | goto err_nortc; | |
480 | } | |
eaa6e4dd | 481 | |
5a5b614b MS |
482 | s3c_rtc_disable_clk(info); |
483 | ||
1add6781 BD |
484 | return 0; |
485 | ||
fc1afe60 | 486 | err_nortc: |
ae05c950 CC |
487 | if (info->data->disable) |
488 | info->data->disable(info); | |
24e14554 CC |
489 | |
490 | if (info->data->needs_src_clk) | |
491 | clk_disable_unprepare(info->rtc_src_clk); | |
8768e7b3 | 492 | err_src_clk: |
19be09f5 | 493 | clk_disable_unprepare(info->rtc_clk); |
1add6781 | 494 | |
1add6781 BD |
495 | return ret; |
496 | } | |
497 | ||
32e445aa | 498 | #ifdef CONFIG_PM_SLEEP |
1add6781 | 499 | |
32e445aa | 500 | static int s3c_rtc_suspend(struct device *dev) |
1add6781 | 501 | { |
19be09f5 | 502 | struct s3c_rtc *info = dev_get_drvdata(dev); |
498bcf31 | 503 | int ret; |
32e445aa | 504 | |
498bcf31 KK |
505 | ret = s3c_rtc_enable_clk(info); |
506 | if (ret) | |
507 | return ret; | |
ae05c950 | 508 | |
ae05c950 CC |
509 | if (info->data->disable) |
510 | info->data->disable(info); | |
f501ed52 | 511 | |
19be09f5 CC |
512 | if (device_may_wakeup(dev) && !info->wake_en) { |
513 | if (enable_irq_wake(info->irq_alarm) == 0) | |
514 | info->wake_en = true; | |
52cd4e5c | 515 | else |
32e445aa | 516 | dev_err(dev, "enable_irq_wake failed\n"); |
52cd4e5c | 517 | } |
ae05c950 | 518 | |
1add6781 BD |
519 | return 0; |
520 | } | |
521 | ||
32e445aa | 522 | static int s3c_rtc_resume(struct device *dev) |
1add6781 | 523 | { |
19be09f5 | 524 | struct s3c_rtc *info = dev_get_drvdata(dev); |
9f4123b7 | 525 | |
ae05c950 CC |
526 | if (info->data->enable) |
527 | info->data->enable(info); | |
528 | ||
24e14554 CC |
529 | s3c_rtc_disable_clk(info); |
530 | ||
19be09f5 CC |
531 | if (device_may_wakeup(dev) && info->wake_en) { |
532 | disable_irq_wake(info->irq_alarm); | |
533 | info->wake_en = false; | |
52cd4e5c | 534 | } |
ae05c950 | 535 | |
1add6781 BD |
536 | return 0; |
537 | } | |
1add6781 | 538 | #endif |
32e445aa JH |
539 | static SIMPLE_DEV_PM_OPS(s3c_rtc_pm_ops, s3c_rtc_suspend, s3c_rtc_resume); |
540 | ||
ae05c950 CC |
541 | static void s3c24xx_rtc_irq(struct s3c_rtc *info, int mask) |
542 | { | |
ae05c950 | 543 | rtc_update_irq(info->rtc, 1, RTC_AF | RTC_IRQF); |
ae05c950 CC |
544 | } |
545 | ||
546 | static void s3c6410_rtc_irq(struct s3c_rtc *info, int mask) | |
547 | { | |
ae05c950 CC |
548 | rtc_update_irq(info->rtc, 1, RTC_AF | RTC_IRQF); |
549 | writeb(mask, info->base + S3C2410_INTP); | |
ae05c950 CC |
550 | } |
551 | ||
ae05c950 | 552 | static struct s3c_rtc_data const s3c2410_rtc_data = { |
ae05c950 | 553 | .irq_handler = s3c24xx_rtc_irq, |
ae05c950 CC |
554 | .enable = s3c24xx_rtc_enable, |
555 | .disable = s3c24xx_rtc_disable, | |
556 | }; | |
557 | ||
558 | static struct s3c_rtc_data const s3c2416_rtc_data = { | |
ae05c950 | 559 | .irq_handler = s3c24xx_rtc_irq, |
ae05c950 CC |
560 | .enable = s3c24xx_rtc_enable, |
561 | .disable = s3c24xx_rtc_disable, | |
562 | }; | |
563 | ||
564 | static struct s3c_rtc_data const s3c2443_rtc_data = { | |
ae05c950 | 565 | .irq_handler = s3c24xx_rtc_irq, |
ae05c950 CC |
566 | .enable = s3c24xx_rtc_enable, |
567 | .disable = s3c24xx_rtc_disable, | |
568 | }; | |
569 | ||
570 | static struct s3c_rtc_data const s3c6410_rtc_data = { | |
8792f777 | 571 | .needs_src_clk = true, |
ae05c950 | 572 | .irq_handler = s3c6410_rtc_irq, |
ae05c950 CC |
573 | .enable = s3c24xx_rtc_enable, |
574 | .disable = s3c6410_rtc_disable, | |
c3cba928 TB |
575 | }; |
576 | ||
7d6bec28 | 577 | static const __maybe_unused struct of_device_id s3c_rtc_dt_match[] = { |
d2524caa | 578 | { |
cd1e6f9e | 579 | .compatible = "samsung,s3c2410-rtc", |
21df6fed | 580 | .data = &s3c2410_rtc_data, |
25c1a246 | 581 | }, { |
cd1e6f9e | 582 | .compatible = "samsung,s3c2416-rtc", |
21df6fed | 583 | .data = &s3c2416_rtc_data, |
25c1a246 | 584 | }, { |
cd1e6f9e | 585 | .compatible = "samsung,s3c2443-rtc", |
21df6fed | 586 | .data = &s3c2443_rtc_data, |
d2524caa | 587 | }, { |
cd1e6f9e | 588 | .compatible = "samsung,s3c6410-rtc", |
21df6fed | 589 | .data = &s3c6410_rtc_data, |
df9e26d0 CC |
590 | }, { |
591 | .compatible = "samsung,exynos3250-rtc", | |
21df6fed | 592 | .data = &s3c6410_rtc_data, |
d2524caa | 593 | }, |
ae05c950 | 594 | { /* sentinel */ }, |
39ce4084 TA |
595 | }; |
596 | MODULE_DEVICE_TABLE(of, s3c_rtc_dt_match); | |
9f4123b7 MC |
597 | |
598 | static struct platform_driver s3c_rtc_driver = { | |
1add6781 | 599 | .probe = s3c_rtc_probe, |
9e6a2ad1 | 600 | .remove_new = s3c_rtc_remove, |
1add6781 | 601 | .driver = { |
9f4123b7 | 602 | .name = "s3c-rtc", |
32e445aa | 603 | .pm = &s3c_rtc_pm_ops, |
04a373fd | 604 | .of_match_table = of_match_ptr(s3c_rtc_dt_match), |
1add6781 BD |
605 | }, |
606 | }; | |
0c4eae66 | 607 | module_platform_driver(s3c_rtc_driver); |
1add6781 BD |
608 | |
609 | MODULE_DESCRIPTION("Samsung S3C RTC Driver"); | |
610 | MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); | |
611 | MODULE_LICENSE("GPL"); | |
ad28a07b | 612 | MODULE_ALIAS("platform:s3c2410-rtc"); |