linux-bk/drivers/char/s3c2410-rtc.c
<<
>>
Prefs
   1/* drivers/char/s3c2410_rtc.c
   2 *
   3 * Copyright (c) 2004 Simtec Electronics <linux@simtec.co.uk>
   4 *                    http://www.simtec.co.uk/products/SWLINUX/
   5 *
   6 * This program is free software; you can redistribute it and/or modify
   7 * it under the terms of the GNU General Public License version 2 as
   8 * published by the Free Software Foundation.
   9 *
  10 * S3C2410 Internal RTC Driver
  11 *
  12 *  Changelog:
  13 *      08-Nov-2004     BJD     Initial creation
  14 *      12-Nov-2004     BJD     Added periodic IRQ and PM code
  15 *      22-Nov-2004     BJD     Sign-test on alarm code to check for <0
  16*/
  17
  18#include <linux/module.h>
  19#include <linux/fs.h>
  20#include <linux/string.h>
  21#include <linux/init.h>
  22#include <linux/device.h>
  23#include <linux/interrupt.h>
  24#include <linux/rtc.h>
  25#include <linux/bcd.h>
  26
  27#include <asm/hardware.h>
  28#include <asm/uaccess.h>
  29#include <asm/io.h>
  30#include <asm/irq.h>
  31#include <asm/rtc.h>
  32
  33#include <asm/mach/time.h>
  34
  35#include <asm/hardware/clock.h>
  36#include <asm/arch/regs-rtc.h>
  37
  38/* need this for the RTC_AF definitions */
  39#include <linux/mc146818rtc.h>
  40
  41#undef S3C2410_VA_RTC
  42#define S3C2410_VA_RTC s3c2410_rtc_base
  43
  44static struct resource *s3c2410_rtc_mem;
  45
  46static void __iomem *s3c2410_rtc_base;
  47static int s3c2410_rtc_alarmno = NO_IRQ;
  48static int s3c2410_rtc_tickno  = NO_IRQ;
  49static int s3c2410_rtc_freq    = 1;
  50
  51static DEFINE_SPINLOCK(s3c2410_rtc_pie_lock);
  52
  53/* IRQ Handlers */
  54
  55static irqreturn_t s3c2410_rtc_alarmirq(int irq, void *id, struct pt_regs *r)
  56{
  57        rtc_update(1, RTC_AF | RTC_IRQF);
  58        return IRQ_HANDLED;
  59}
  60
  61static irqreturn_t s3c2410_rtc_tickirq(int irq, void *id, struct pt_regs *r)
  62{
  63        rtc_update(1, RTC_PF | RTC_IRQF);
  64        return IRQ_HANDLED;
  65}
  66
  67/* Update control registers */
  68static void s3c2410_rtc_setaie(int to)
  69{
  70        unsigned int tmp;
  71
  72        pr_debug("%s: aie=%d\n", __FUNCTION__, to);
  73
  74        tmp = readb(S3C2410_RTCALM);
  75
  76        if (to)
  77                tmp |= S3C2410_RTCALM_ALMEN;
  78        else
  79                tmp &= ~S3C2410_RTCALM_ALMEN;
  80
  81
  82        writeb(tmp, S3C2410_RTCALM);
  83}
  84
  85static void s3c2410_rtc_setpie(int to)
  86{
  87        unsigned int tmp;
  88
  89        pr_debug("%s: pie=%d\n", __FUNCTION__, to);
  90
  91        spin_lock_irq(&s3c2410_rtc_pie_lock);
  92        tmp = readb(S3C2410_TICNT) & ~S3C2410_TICNT_ENABLE;
  93
  94        if (to)
  95                tmp |= S3C2410_TICNT_ENABLE;
  96
  97        writeb(tmp, S3C2410_TICNT);
  98        spin_unlock_irq(&s3c2410_rtc_pie_lock);
  99}
 100
 101static void s3c2410_rtc_setfreq(int freq)
 102{
 103        unsigned int tmp;
 104
 105        spin_lock_irq(&s3c2410_rtc_pie_lock);
 106        tmp = readb(S3C2410_TICNT) & S3C2410_TICNT_ENABLE;
 107
 108        s3c2410_rtc_freq = freq;
 109
 110        tmp |= (128 / freq)-1;
 111
 112        writeb(tmp, S3C2410_TICNT);
 113        spin_unlock_irq(&s3c2410_rtc_pie_lock);
 114}
 115
 116/* Time read/write */
 117
 118static void s3c2410_rtc_gettime(struct rtc_time *rtc_tm)
 119{
 120        unsigned int have_retried = 0;
 121
 122 retry_get_time:
 123        rtc_tm->tm_min  = readb(S3C2410_RTCMIN);
 124        rtc_tm->tm_hour = readb(S3C2410_RTCHOUR);
 125        rtc_tm->tm_mday = readb(S3C2410_RTCDATE);
 126        rtc_tm->tm_mon  = readb(S3C2410_RTCMON);
 127        rtc_tm->tm_year = readb(S3C2410_RTCYEAR);
 128        rtc_tm->tm_sec  = readb(S3C2410_RTCSEC);
 129
 130        /* the only way to work out wether the system was mid-update
 131         * when we read it is to check the second counter, and if it
 132         * is zero, then we re-try the entire read
 133         */
 134
 135        if (rtc_tm->tm_sec == 0 && !have_retried) {
 136                have_retried = 1;
 137                goto retry_get_time;
 138        }
 139
 140        pr_debug("read time %02x.%02x.%02x %02x/%02x/%02x\n",
 141                 rtc_tm->tm_year, rtc_tm->tm_mon, rtc_tm->tm_mday,
 142                 rtc_tm->tm_hour, rtc_tm->tm_min, rtc_tm->tm_sec);
 143
 144        BCD_TO_BIN(rtc_tm->tm_sec);
 145        BCD_TO_BIN(rtc_tm->tm_min);
 146        BCD_TO_BIN(rtc_tm->tm_hour);
 147        BCD_TO_BIN(rtc_tm->tm_mday);
 148        BCD_TO_BIN(rtc_tm->tm_mon);
 149        BCD_TO_BIN(rtc_tm->tm_year);
 150
 151        rtc_tm->tm_year += 100;
 152        rtc_tm->tm_mon -= 1;
 153}
 154
 155
 156static int s3c2410_rtc_settime(struct rtc_time *tm)
 157{
 158        /* the rtc gets round the y2k problem by just not supporting it */
 159
 160        if (tm->tm_year < 100)
 161                return -EINVAL;
 162
 163        writeb(BIN2BCD(tm->tm_sec),  S3C2410_RTCSEC);
 164        writeb(BIN2BCD(tm->tm_min),  S3C2410_RTCMIN);
 165        writeb(BIN2BCD(tm->tm_hour), S3C2410_RTCHOUR);
 166        writeb(BIN2BCD(tm->tm_mday), S3C2410_RTCDATE);
 167        writeb(BIN2BCD(tm->tm_mon + 1), S3C2410_RTCMON);
 168        writeb(BIN2BCD(tm->tm_year - 100), S3C2410_RTCYEAR);
 169
 170        return 0;
 171}
 172
 173static void s3c2410_rtc_getalarm(struct rtc_wkalrm *alrm)
 174{
 175        struct rtc_time *alm_tm = &alrm->time;
 176        unsigned int alm_en;
 177
 178        alm_tm->tm_sec  = readb(S3C2410_ALMSEC);
 179        alm_tm->tm_min  = readb(S3C2410_ALMMIN);
 180        alm_tm->tm_hour = readb(S3C2410_ALMHOUR);
 181        alm_tm->tm_mon  = readb(S3C2410_ALMMON);
 182        alm_tm->tm_mday = readb(S3C2410_ALMDATE);
 183        alm_tm->tm_year = readb(S3C2410_ALMYEAR);
 184
 185        alm_en = readb(S3C2410_RTCALM);
 186
 187        pr_debug("read alarm %02x %02x.%02x.%02x %02x/%02x/%02x\n",
 188                 alm_en,
 189                 alm_tm->tm_year, alm_tm->tm_mon, alm_tm->tm_mday,
 190                 alm_tm->tm_hour, alm_tm->tm_min, alm_tm->tm_sec);
 191
 192
 193        /* decode the alarm enable field */
 194
 195        if (alm_en & S3C2410_RTCALM_SECEN) {
 196                BCD_TO_BIN(alm_tm->tm_sec);
 197        } else {
 198                alm_tm->tm_sec = 0xff;
 199        }
 200
 201        if (alm_en & S3C2410_RTCALM_MINEN) {
 202                BCD_TO_BIN(alm_tm->tm_min);
 203        } else {
 204                alm_tm->tm_min = 0xff;
 205        }
 206
 207        if (alm_en & S3C2410_RTCALM_HOUREN) {
 208                BCD_TO_BIN(alm_tm->tm_hour);
 209        } else {
 210                alm_tm->tm_hour = 0xff;
 211        }
 212
 213        if (alm_en & S3C2410_RTCALM_DAYEN) {
 214                BCD_TO_BIN(alm_tm->tm_mday);
 215        } else {
 216                alm_tm->tm_mday = 0xff;
 217        }
 218
 219        if (alm_en & S3C2410_RTCALM_MONEN) {
 220                BCD_TO_BIN(alm_tm->tm_mon);
 221                alm_tm->tm_mon -= 1;
 222        } else {
 223                alm_tm->tm_mon = 0xff;
 224        }
 225
 226        if (alm_en & S3C2410_RTCALM_YEAREN) {
 227                BCD_TO_BIN(alm_tm->tm_year);
 228        } else {
 229                alm_tm->tm_year = 0xffff;
 230        }
 231
 232        /* todo - set alrm->enabled ? */
 233}
 234
 235static int s3c2410_rtc_setalarm(struct rtc_wkalrm *alrm)
 236{
 237        struct rtc_time *tm = &alrm->time;
 238        unsigned int alrm_en;
 239
 240        pr_debug("s3c2410_rtc_setalarm: %d, %02x/%02x/%02x %02x.%02x.%02x\n",
 241                 alrm->enabled,
 242                 tm->tm_mday & 0xff, tm->tm_mon & 0xff, tm->tm_year & 0xff,
 243                 tm->tm_hour & 0xff, tm->tm_min & 0xff, tm->tm_sec);
 244
 245        if (alrm->enabled || 1) {
 246                alrm_en = readb(S3C2410_RTCALM) & S3C2410_RTCALM_ALMEN;
 247                writeb(0x00, S3C2410_RTCALM);
 248
 249                if (tm->tm_sec < 60 && tm->tm_sec >= 0) {
 250                        alrm_en |= S3C2410_RTCALM_SECEN;
 251                        writeb(BIN2BCD(tm->tm_sec), S3C2410_ALMSEC);
 252                }
 253
 254                if (tm->tm_min < 60 && tm->tm_min >= 0) {
 255                        alrm_en |= S3C2410_RTCALM_MINEN;
 256                        writeb(BIN2BCD(tm->tm_min), S3C2410_ALMMIN);
 257                }
 258
 259                if (tm->tm_hour < 24 && tm->tm_hour >= 0) {
 260                        alrm_en |= S3C2410_RTCALM_HOUREN;
 261                        writeb(BIN2BCD(tm->tm_hour), S3C2410_ALMHOUR);
 262                }
 263
 264                pr_debug("setting S3C2410_RTCALM to %08x\n", alrm_en);
 265
 266                writeb(alrm_en, S3C2410_RTCALM);
 267                enable_irq_wake(s3c2410_rtc_alarmno);
 268        } else {
 269                alrm_en = readb(S3C2410_RTCALM);
 270                alrm_en &= ~S3C2410_RTCALM_ALMEN;
 271                writeb(alrm_en, S3C2410_RTCALM);
 272                disable_irq_wake(s3c2410_rtc_alarmno);
 273        }
 274
 275        return 0;
 276}
 277
 278static int s3c2410_rtc_ioctl(unsigned int cmd, unsigned long arg)
 279{
 280        switch (cmd) {
 281        case RTC_AIE_OFF:
 282        case RTC_AIE_ON:
 283                s3c2410_rtc_setaie((cmd == RTC_AIE_ON) ? 1 : 0);
 284                return 0;
 285
 286        case RTC_PIE_OFF:
 287        case RTC_PIE_ON:
 288                s3c2410_rtc_setpie((cmd == RTC_PIE_ON) ? 1 : 0);
 289                return 0;
 290
 291        case RTC_IRQP_READ:
 292                return put_user(s3c2410_rtc_freq, (unsigned long __user *)arg);
 293
 294        case RTC_IRQP_SET:
 295                if (arg < 1 || arg > 64)
 296                        return -EINVAL;
 297
 298                if (!capable(CAP_SYS_RESOURCE))
 299                        return -EACCES;
 300
 301                /* check for power of 2 */
 302
 303                if ((arg & (arg-1)) != 0)
 304                        return -EINVAL;
 305
 306                pr_debug("s3c2410_rtc: setting frequency %ld\n", arg);
 307
 308                s3c2410_rtc_setfreq(arg);
 309                return 0;
 310
 311        case RTC_UIE_ON:
 312        case RTC_UIE_OFF:
 313                return -EINVAL;
 314        }
 315
 316        return -EINVAL;
 317}
 318
 319static int s3c2410_rtc_proc(char *buf)
 320{
 321        unsigned int rtcalm = readb(S3C2410_RTCALM);
 322        unsigned int ticnt = readb (S3C2410_TICNT);
 323        char *p = buf;
 324
 325        p += sprintf(p, "alarm_IRQ\t: %s\n",
 326                     (rtcalm & S3C2410_RTCALM_ALMEN) ? "yes" : "no" );
 327        p += sprintf(p, "periodic_IRQ\t: %s\n",
 328                     (ticnt & S3C2410_TICNT_ENABLE) ? "yes" : "no" );
 329        p += sprintf(p, "periodic_freq\t: %d\n", s3c2410_rtc_freq);
 330
 331        return p - buf;
 332}
 333
 334static int s3c2410_rtc_open(void)
 335{
 336        int ret;
 337
 338        ret = request_irq(s3c2410_rtc_alarmno, s3c2410_rtc_alarmirq,
 339                          SA_INTERRUPT,  "s3c2410-rtc alarm", NULL);
 340
 341        if (ret)
 342                printk(KERN_ERR "IRQ%d already in use\n", s3c2410_rtc_alarmno);
 343
 344        ret = request_irq(s3c2410_rtc_tickno, s3c2410_rtc_tickirq,
 345                          SA_INTERRUPT,  "s3c2410-rtc tick", NULL);
 346
 347        if (ret) {
 348                printk(KERN_ERR "IRQ%d already in use\n", s3c2410_rtc_tickno);
 349                goto tick_err;
 350        }
 351
 352        return ret;
 353
 354 tick_err:
 355        free_irq(s3c2410_rtc_alarmno, NULL);
 356        return ret;
 357}
 358
 359static void s3c2410_rtc_release(void)
 360{
 361        /* do not clear AIE here, it may be needed for wake */
 362
 363        s3c2410_rtc_setpie(0);
 364        free_irq(s3c2410_rtc_alarmno, NULL);
 365        free_irq(s3c2410_rtc_tickno, NULL);
 366}
 367
 368static struct rtc_ops s3c2410_rtcops = {
 369        .owner          = THIS_MODULE,
 370        .open           = s3c2410_rtc_open,
 371        .release        = s3c2410_rtc_release,
 372        .ioctl          = s3c2410_rtc_ioctl,
 373        .read_time      = s3c2410_rtc_gettime,
 374        .set_time       = s3c2410_rtc_settime,
 375        .read_alarm     = s3c2410_rtc_getalarm,
 376        .set_alarm      = s3c2410_rtc_setalarm,
 377        .proc           = s3c2410_rtc_proc,
 378};
 379
 380static void s3c2410_rtc_enable(struct device *dev, int en)
 381{
 382        unsigned int tmp;
 383
 384        if (s3c2410_rtc_base == NULL)
 385                return;
 386
 387        if (!en) {
 388                tmp = readb(S3C2410_RTCCON);
 389                writeb(tmp & ~S3C2410_RTCCON_RTCEN, S3C2410_RTCCON);
 390
 391                tmp = readb(S3C2410_TICNT);
 392                writeb(tmp & ~S3C2410_TICNT_ENABLE, S3C2410_TICNT);
 393        } else {
 394                /* re-enable the device, and check it is ok */
 395
 396                if ((readb(S3C2410_RTCCON) & S3C2410_RTCCON_RTCEN) == 0){
 397                        dev_info(dev, "rtc disabled, re-enabling\n");
 398
 399                        tmp = readb(S3C2410_RTCCON);
 400                        writeb(tmp | S3C2410_RTCCON_RTCEN , S3C2410_RTCCON);
 401                }
 402
 403                if ((readb(S3C2410_RTCCON) & S3C2410_RTCCON_CNTSEL)){
 404                        dev_info(dev, "removing S3C2410_RTCCON_CNTSEL\n");
 405
 406                        tmp = readb(S3C2410_RTCCON);
 407                        writeb(tmp& ~S3C2410_RTCCON_CNTSEL , S3C2410_RTCCON);
 408                }
 409
 410                if ((readb(S3C2410_RTCCON) & S3C2410_RTCCON_CLKRST)){
 411                        dev_info(dev, "removing S3C2410_RTCCON_CLKRST\n");
 412
 413                        tmp = readb(S3C2410_RTCCON);
 414                        writeb(tmp & ~S3C2410_RTCCON_CLKRST, S3C2410_RTCCON);
 415                }
 416        }
 417}
 418
 419static int s3c2410_rtc_remove(struct device *dev)
 420{
 421        unregister_rtc(&s3c2410_rtcops);
 422
 423        s3c2410_rtc_setpie(0);
 424        s3c2410_rtc_setaie(0);
 425
 426        if (s3c2410_rtc_mem != NULL) {
 427                pr_debug("s3c2410_rtc: releasing s3c2410_rtc_mem\n");
 428                iounmap(s3c2410_rtc_base);
 429                release_resource(s3c2410_rtc_mem);
 430                kfree(s3c2410_rtc_mem);
 431        }
 432
 433        return 0;
 434}
 435
 436static int s3c2410_rtc_probe(struct device *dev)
 437{
 438        struct platform_device *pdev = to_platform_device(dev);
 439        struct resource *res;
 440        int ret;
 441
 442        pr_debug("%s: probe=%p, device=%p\n", __FUNCTION__, pdev, dev);
 443
 444        /* find the IRQs */
 445
 446        s3c2410_rtc_tickno = platform_get_irq(pdev, 1);
 447        if (s3c2410_rtc_tickno <= 0) {
 448                dev_err(dev, "no irq for rtc tick\n");
 449                return -ENOENT;
 450        }
 451
 452        s3c2410_rtc_alarmno = platform_get_irq(pdev, 0);
 453        if (s3c2410_rtc_alarmno <= 0) {
 454                dev_err(dev, "no irq for alarm\n");
 455                return -ENOENT;
 456        }
 457
 458        pr_debug("s3c2410_rtc: tick irq %d, alarm irq %d\n",
 459                 s3c2410_rtc_tickno, s3c2410_rtc_alarmno);
 460
 461        /* get the memory region */
 462
 463        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 464        if (res == NULL) {
 465                dev_err(dev, "failed to get memory region resource\n");
 466                return -ENOENT;
 467        }
 468
 469        s3c2410_rtc_mem = request_mem_region(res->start, res->end-res->start+1,
 470                                     pdev->name);
 471
 472        if (s3c2410_rtc_mem == NULL) {
 473                dev_err(dev, "failed to reserve memory region\n");
 474                ret = -ENOENT;
 475                goto exit_err;
 476        }
 477
 478        s3c2410_rtc_base = ioremap(res->start, res->end - res->start + 1);
 479        if (s3c2410_rtc_base == NULL) {
 480                dev_err(dev, "failed ioremap()\n");
 481                ret = -EINVAL;
 482                goto exit_err;
 483        }
 484
 485        s3c2410_rtc_mem = res;
 486        pr_debug("s3c2410_rtc_base=%p\n", s3c2410_rtc_base);
 487
 488        pr_debug("s3c2410_rtc: RTCCON=%02x\n", readb(S3C2410_RTCCON));
 489
 490        /* check to see if everything is setup correctly */
 491
 492        s3c2410_rtc_enable(dev, 1);
 493
 494        pr_debug("s3c2410_rtc: RTCCON=%02x\n", readb(S3C2410_RTCCON));
 495
 496        s3c2410_rtc_setfreq(s3c2410_rtc_freq);
 497
 498        /* register RTC and exit */
 499
 500        register_rtc(&s3c2410_rtcops);
 501        return 0;
 502
 503 exit_err:
 504        dev_err(dev, "error %d during initialisation\n", ret);
 505
 506        return ret;
 507}
 508
 509#ifdef CONFIG_PM
 510
 511/* S3C2410 RTC Power management control */
 512
 513static struct timespec s3c2410_rtc_delta;
 514
 515static int ticnt_save;
 516
 517static int s3c2410_rtc_suspend(struct device *dev, u32 state, u32 level)
 518{
 519        struct rtc_time tm;
 520        struct timespec time;
 521
 522        time.tv_nsec = 0;
 523
 524        if (level == SUSPEND_POWER_DOWN) {
 525                /* save TICNT for anyone using periodic interrupts */
 526
 527                ticnt_save = readb(S3C2410_TICNT);
 528
 529                /* calculate time delta for suspend */
 530
 531                s3c2410_rtc_gettime(&tm);
 532                rtc_tm_to_time(&tm, &time.tv_sec);
 533                save_time_delta(&s3c2410_rtc_delta, &time);
 534                s3c2410_rtc_enable(dev, 0);
 535        }
 536
 537        return 0;
 538}
 539
 540static int s3c2410_rtc_resume(struct device *dev, u32 level)
 541{
 542        struct rtc_time tm;
 543        struct timespec time;
 544
 545        time.tv_nsec = 0;
 546
 547        s3c2410_rtc_enable(dev, 1);
 548        s3c2410_rtc_gettime(&tm);
 549        rtc_tm_to_time(&tm, &time.tv_sec);
 550        restore_time_delta(&s3c2410_rtc_delta, &time);
 551
 552        writeb(ticnt_save, S3C2410_TICNT);
 553        return 0;
 554}
 555#else
 556#define s3c2410_rtc_suspend NULL
 557#define s3c2410_rtc_resume  NULL
 558#endif
 559
 560static struct device_driver s3c2410_rtcdrv = {
 561        .name           = "s3c2410-rtc",
 562        .bus            = &platform_bus_type,
 563        .probe          = s3c2410_rtc_probe,
 564        .remove         = s3c2410_rtc_remove,
 565        .suspend        = s3c2410_rtc_suspend,
 566        .resume         = s3c2410_rtc_resume,
 567};
 568
 569static char __initdata banner[] = "S3C2410 RTC, (c) 2004 Simtec Electronics\n";
 570
 571static int __init s3c2410_rtc_init(void)
 572{
 573        printk(banner);
 574        return driver_register(&s3c2410_rtcdrv);
 575}
 576
 577static void __exit s3c2410_rtc_exit(void)
 578{
 579        driver_unregister(&s3c2410_rtcdrv);
 580}
 581
 582module_init(s3c2410_rtc_init);
 583module_exit(s3c2410_rtc_exit);
 584
 585MODULE_DESCRIPTION("S3C24XX RTC Driver");
 586MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
 587MODULE_LICENSE("GPL");
 588
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.