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