diff options
Diffstat (limited to 'drivers/rtc/wmt-rtc.c')
-rwxr-xr-x | drivers/rtc/wmt-rtc.c | 746 |
1 files changed, 746 insertions, 0 deletions
diff --git a/drivers/rtc/wmt-rtc.c b/drivers/rtc/wmt-rtc.c new file mode 100755 index 00000000..eeed9fba --- /dev/null +++ b/drivers/rtc/wmt-rtc.c @@ -0,0 +1,746 @@ +/*++ +Copyright (c) 2008 WonderMedia Technologies, Inc. + +This program is free software: you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the Free Software Foundation, +either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU General Public License for more details. +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. + +WonderMedia Technologies, Inc. +10F, 529, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C. +--*/ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/string.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/rtc.h> + +#include <linux/bitops.h> +#include <linux/pm.h> +#include <mach/hardware.h> +#include <asm/irq.h> + +#ifndef DPRINTK +#define DPRINTK printk +#endif + +#define DRIVER_VERSION "0.54" + +#define RTC_DEF_FREQ (32768) +#define RTC_DEF_DIVIDER (32768 - 1) +#define IRQ_RTC_ALARM IRQ_RTC1 +#define IRQ_RTC_UPDATE IRQ_RTC2 +/* + * Following are the bits from a classic RTC we want to mimic + */ +#define RTC_IRQF 0x80 /* any of the following 3 is active */ +#define RTC_PF 0x40 +#define RTC_AF 0x20 +#define RTC_UF 0x10 + +struct rtc_regs_t { + unsigned int volatile RTTS; /* RTC Time Set Register */ + unsigned int volatile RTDS; /* RTC Date Set Register */ + unsigned int volatile RTAS; /* RTC Alarm Set Register */ + unsigned int volatile RTCC; /* RTC Control Register */ + unsigned int volatile RTCT; /* RTC Current Time Register */ + unsigned int volatile RTCD; /* RTC Current Date Register */ + unsigned int volatile RTWS; /* RTC Write Status Register */ + unsigned int volatile RTTM; /* RTC Test Mode Register */ + unsigned int volatile RTTC; /* RTC Time Calibration Register */ + unsigned int volatile RTIS; /* RTC Interrupt Status Register */ +}; + +/* + * For RTTS and RTCT registers + */ +struct bcd_time_t { + unsigned int sec:7; /* second */ + unsigned int min:7; /* minute */ + unsigned int hour:6; /* hour */ + unsigned int wday:3; /* day of week */ + unsigned int rev:8; /* reversed */ + unsigned int flag:1; /* invalid flag */ +}; + +/* + * For RTDS and RTCD registers + */ +struct bcd_date_t { + unsigned int mday:6; /* day of month */ + unsigned int mon:5; /* month */ + unsigned int year:8; /* year */ + unsigned int cen:1; /* century after 2k */ + unsigned int rev:11; /* reversed */ + unsigned int flag:1; /* invalid flag */ +}; + +/* + * For RTAS register + */ +struct bcd_alarm_t { + unsigned int sec:7; /* second */ + unsigned int min:7; /* minute */ + unsigned int hour:6; /* hour */ + unsigned int mday:6; /* day of week */ + unsigned int cmp:4; /* compare */ + unsigned int rev:2; /* reversed */ +}; + +struct wmt_alarm_t { + int type; + int sec; /* seconds after the minute - [0,59] */ + int min; /* minutes after the hour - [0,59] */ + int hour; /* hours since midnight - [0,23] */ + int mday; /* day of the month - [1,31] */ +}; + +/* + * WMT RTC operation structure. + */ +struct wmt_rtc_t { + struct rtc_regs_t *const regs; /* register set */ + unsigned int rtct; + unsigned int rtcd; + unsigned int rtas; +}; + +static struct wmt_rtc_t rtc = { + /* register set */ + (struct rtc_regs_t *)(io_p2v(__RTC_BASE)), + /* shadow registers */ + 0, 0, 0, +}; + +/* + * This struct keep the alarm setting. + */ +static struct rtc_time rtc_alarm = { + .tm_year = 100, /* 2000 */ + .tm_mon = 0, /* Jan */ + .tm_mday = 1, /* 1st */ + .tm_hour = 0, + .tm_min = 0, + .tm_sec = 0, +}; + +#ifdef CONFIG_RTC_DRV_CMOS_MODULE +extern spinlock_t rtc_lock; /* in $ARCH\kernel\time.c */ +#else +DEFINE_SPINLOCK(rtc_lock); +#endif + +extern int wmt_rtc_on; + +/* wmt_set_time() + * + * Write new setting to RTC Time Set Register. + * + * Note: Check RTC Write Stauts Register first before writing + * new value to RTC Time Set Register. + */ +static int wmt_set_time(unsigned int value) +{ + int ret = 0; + + /* + * Check for free writing + */ + if ((rtc.regs->RTWS & RTWS_TIMESET) == 0) { + rtc.regs->RTTS = (value & RTTS_TIME); + } else { + printk(KERN_ERR "rtc_err : RTTS register write busy!\n"); + ret = -EBUSY; + } + return ret; +} + +/* wmt_set_date() + * + * Write new setting to RTC Date Set Register. + * + * Note: Check RTC Write Stauts Register first before writing + * new value to RTC Date Set Register. + */ +static int wmt_set_date(unsigned int value) +{ + int ret = 0; + + /* + * Check for free writing + */ + if ((rtc.regs->RTWS & RTWS_DATESET) == 0) + rtc.regs->RTDS = (value & RTDS_DATE); + else + ret = -EBUSY; + + return ret; +} + +/* wmt_set_alarm() + * + * Parsing tm structure and then set to RTAS register. + * + * Note: Check RTC Write Stauts Register first before writing + * new value to RTC Alarm Set Register. + * Current policy only can set the alarm to day of month. + */ +static int wmt_set_alarm(struct rtc_time *alarm) +{ + int ret = 0; + unsigned int value; + + /* + * Following one is a good example if you wanna + * see the result in RTAS register. + * You can clearly see BCD in time and date structure. + */ + + /* + * Check structure "alarm". + */ + value = 0; +printk(KERN_ERR "** rtc %d %d %d\n",alarm->tm_hour,alarm->tm_min,alarm->tm_sec); + + if ((alarm->tm_sec >= 0) && (alarm->tm_sec < 60)) { /* [0,59] valid */ + value |= RTAS_SEC(alarm->tm_sec); + value |= RTAS_CMPSEC; + } + + if ((alarm->tm_min >= 0) && (alarm->tm_min < 60)) { /* [0,59] valid */ + value |= RTAS_MIN(alarm->tm_min); + value |= RTAS_CMPMIN; + } + + if ((alarm->tm_hour >= 0) && (alarm->tm_hour < 24)) { /* [0,23] valid */ + value |= RTAS_HOUR(alarm->tm_hour); + value |= RTAS_CMPHOUR; + } + + if ((alarm->tm_mday > 0) && (alarm->tm_mday <= 31)) { /* [1,31] valid */ + value |= RTAS_DAY(alarm->tm_mday); + value |= RTAS_CMPDAY; + } + + /* + * Update new alarm time. + */ + if (value) { + if ((rtc.regs->RTWS & RTWS_ALARMSET) == 0) /* write free */ + rtc.regs->RTAS = value; + else + ret = -EBUSY; + } + return ret; +} + +static inline int rtc_periodic_alarm(struct rtc_time *tm) +{ + return (tm->tm_year == -1) || + ((unsigned)tm->tm_mon >= 12) || + ((unsigned)(tm->tm_mday - 1) >= 31) || + ((unsigned)tm->tm_hour > 23) || + ((unsigned)tm->tm_min > 59) || + ((unsigned)tm->tm_sec > 59); +} + + +static irqreturn_t +wmt_rtc_interrupt(int irq, void *dev_id) +{ + struct platform_device *pdev = to_platform_device(dev_id); + struct rtc_device *wmt_rtc = platform_get_drvdata(pdev); + unsigned int rtis; + unsigned long events = 0; + + spin_lock(&rtc_lock); + /* + * Save status in a shadow register + * Clear interrupt source. + */ + rtis = RTIS_VAL; + RTIS_VAL = rtis; + + /* + * Clear alarm interrupt if it has occurred + */ + if (rtis & RTIS_ALARM) + rtc.regs->RTAS &= ~RTAS_CMPMASK; + + /* + * Update irq data & counter + */ + if (rtis & RTIS_ALARM) + events |= (RTC_AF|RTC_IRQF); + if (rtis & RTIS_UPDATE) + events |= (RTC_UF|RTC_IRQF); + + if (wmt_rtc != NULL) + rtc_update_irq(wmt_rtc, 1, events); /* in rtctime.c */ + else + printk("Warning : wmt_rtc is NULL , will not pass intr status to kernel\n"); + + if ((rtis & RTIS_ALARM) && rtc_periodic_alarm(&rtc_alarm)) + wmt_set_alarm(&rtc_alarm); /* in rtctime.c */ + + spin_unlock(&rtc_lock); + + return IRQ_HANDLED; +} + +static int wmt_rtc_open(struct device *dev) +{ + int ret; + + ret = request_irq(IRQ_RTC_UPDATE, + wmt_rtc_interrupt, + IRQF_DISABLED, + "rtc_ticks", + NULL); + if (ret) { + printk(KERN_ERR "rtc: IRQ%d already in use.\n", IRQ_RTC_UPDATE); + goto fail_update; + } + + ret = request_irq(IRQ_RTC_ALARM, + wmt_rtc_interrupt, + IRQF_DISABLED, + "rtc_alarm", + NULL); + if (ret) { + printk(KERN_ERR "rtc: IRQ%d already in use.\n", IRQ_RTC_ALARM); + goto fail_alarm; + } + + return 0; + +fail_alarm: + free_irq(IRQ_RTC_UPDATE, NULL); +fail_update: + return ret; +} + +static void wmt_rtc_release(struct device *dev) +{ + spin_lock_irq(&rtc_lock); + /* + * Release RTC resource. + */ + rtc.regs->RTCC &= ~RTCC_INTENA; + rtc.regs->RTAS = 0; + rtc.rtas = 0; + + spin_unlock_irq(&rtc_lock); + + /* + * Release IRQ resource. + */ + free_irq(IRQ_RTC_UPDATE, NULL); + free_irq(IRQ_RTC_ALARM, NULL); +} + +static int wmt_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case RTC_AIE_OFF: + /* + * Disable Alarm Interrupt + */ + spin_lock_irq(&rtc_lock); + /* + * Save RTAS to shadow register. + */ + rtc.rtas = rtc.regs->RTAS; + rtc.regs->RTAS &= ~RTAS_CMPMASK; + spin_unlock_irq(&rtc_lock); + return 0; + case RTC_AIE_ON: + /* + * Enable Alarm Interrupt + */ + spin_lock_irq(&rtc_lock); + /* + * Since the alarm time setting was not changed, + * we only recover compare bits from shadow register. + */ + rtc.regs->RTAS |= (rtc.rtas & RTAS_CMPMASK); + spin_unlock_irq(&rtc_lock); + return 0; + case RTC_UIE_OFF: + /* + * Disable Update Interrupt + */ + spin_lock_irq(&rtc_lock); + rtc.regs->RTCC &= ~RTCC_INTENA; + spin_unlock_irq(&rtc_lock); + return 0; + case RTC_UIE_ON: + /* + * Enable Update Interrupt + */ + spin_lock_irq(&rtc_lock); + rtc.regs->RTCC |= RTCC_INTENA; + spin_unlock_irq(&rtc_lock); + return 0; + + /* + * Current RTC driver does not support following ioctls + */ + + case RTC_PIE_OFF: /* Disable Periodic Interrupt */ + case RTC_PIE_ON: /* Enable Periodic Interrupt */ + case RTC_IRQP_READ: /* Read IRQ rate */ + case RTC_IRQP_SET: /* Set IRQ rate */ + return -EINVAL; + } + return -EINVAL; +} + +static int wmt_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + if (tm == NULL) { /* for debugging */ + printk(KERN_WARNING "RTC: %s *tm is invalid!\n", __func__); + return -EINVAL; + } + + rtc.rtct = rtc.regs->RTCT; + rtc.rtcd = rtc.regs->RTCD; + + if ((rtc.rtct & RTCT_INVALID) || (rtc.rtcd & RTCD_INVALID)) { + printk(KERN_WARNING "RTC: RTCD/RTCT register invalid flag on!\n"); + printk(KERN_WARNING "RTC: RTCD=0x%.8x RTCT=0x%.8x\n", + rtc.regs->RTCD, + rtc.regs->RTCT); + return -EINVAL; + } + + /* + * BCD2BIN translation + */ + tm->tm_sec = RTCT_SEC(rtc.rtct); + + tm->tm_min = RTCT_MIN(rtc.rtct); + + tm->tm_hour = RTCT_HOUR(rtc.rtct); + + tm->tm_wday = RTCT_DAY(rtc.rtct); + + tm->tm_mday = RTCD_MDAY(rtc.rtcd); + + tm->tm_mon = RTCD_MON(rtc.rtcd) - 1; /* 0 means January */ + + tm->tm_year = RTCD_YEAR(rtc.rtcd) + ((RTCD_CENT(rtc.rtcd)) * 100 + 100); + + return 0; +} + +/* wmt_rtc_set_time() + * + * Setup the RTC date and time. + * + * In: tm, a rtc_time structure. + */ +static int wmt_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + int ret = 0; + unsigned int value; + /* + * Following two are good examples if you wanna + * see the result in RTTS and RTDS registers. + * You can clearly see BCD in time and date structure. + */ + + if ((tm->tm_sec < 0) || (tm->tm_sec > 59)) { /* [0,59] valid */ + printk(KERN_ERR "rtc_err : invalid sec\n"); + ret = -EINVAL; + goto out; + } + + if ((tm->tm_min < 0) || (tm->tm_min > 59)) { /* [0,59] valid */ + printk(KERN_ERR "rtc_err : invalid min\n"); + ret = -EINVAL; + goto out; + } + + if ((tm->tm_hour < 0) || (tm->tm_hour > 23)) { /* [0,23] valid */ + printk(KERN_ERR "rtc_err : invalid hour\n"); + ret = -EINVAL; + goto out; + } + + if ((tm->tm_wday < 0) || (tm->tm_wday > 6)) { /* [0,6] valid */ + printk(KERN_ERR "rtc_err : invalid wday\n"); + ret = -EINVAL; + goto out; + } + + if ((tm->tm_mday < 0) || (tm->tm_mday > 31)) { /* [1,31] valid */ + printk(KERN_ERR "rtc_err : invalid mday\n"); + ret = -EINVAL; + goto out; + } + + if ((tm->tm_mon < 0) || (tm->tm_mon > 11)) { /* [0,11] valid */ + printk(KERN_ERR "rtc_err : invalid mon\n"); + ret = -EINVAL; + goto out; + } + + if (tm->tm_year < 100) { /* >= 100 valid */ + printk(KERN_ERR "rtc_err : invalid year\n"); + ret = -EINVAL; + goto out; + } + + value = RTTS_SEC(tm->tm_sec) | RTTS_MIN(tm->tm_min) | \ + RTTS_HOUR(tm->tm_hour) | RTTS_DAY(tm->tm_wday); + + ret = wmt_set_time(value); + + if (ret) + goto out; + + value = RTDS_MDAY(tm->tm_mday) | RTDS_MON(tm->tm_mon + 1) | \ + RTDS_YEAR(tm->tm_year%100) | RTDS_CENT((tm->tm_year/100) - 1); + + ret = wmt_set_date(value); +out: + return ret; +} + +static int wmt_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + memcpy(&alrm->time, &rtc_alarm, sizeof(struct rtc_time)); + alrm->pending = ((rtc.regs->RTIS & RTIS_ALARM) ? 1 : 0); + return 0; +} + +/* + * Handle wakeup alarm API information defined in include/linux/rtc.h + */ +static int wmt_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + int ret; + + spin_lock_irq(&rtc_lock); + + ret = wmt_set_alarm(&alrm->time); + + if (ret == 0) { + memcpy(&rtc_alarm, &alrm->time, sizeof(struct rtc_time)); + if (alrm->enabled) + enable_irq_wake(IRQ_RTC_ALARM); + else + disable_irq_wake(IRQ_RTC_ALARM); + } + spin_unlock_irq(&rtc_lock); + + return ret; +} + +static int wmt_rtc_proc(struct device *dev, struct seq_file *seq) +{ + +// char *p = seq->buf; +// int ret; + + int is_testmode = (RTTM_VAL & RTTM_ENABLE); +/* + p += sprintf(p, "test_mode\t: %s\n", + (is_testmode) ? "enabled" : "disabled"); + + p += sprintf(p, "%s\t: 0x%08x\n", + ((is_testmode) ? "divider" : "calibration"), + RTTC_VAL); + + p += sprintf(p, "alarm_IRQ\t: %s\n", + (RTIS_VAL & RTIS_ALARM) ? "yes" : "no"); + + p += sprintf(p, "ticks_IRQ\t: %s\n", + (RTIS_VAL & RTIS_UPDATE) ? "yes" : "no"); + + ret = p - seq->buf; + + return ret; +*/ + + seq_printf(seq, "test_mode\t: %s\n", + (is_testmode) ? "enabled" : "disabled"); + + seq_printf(seq, "%s\t: 0x%08x\n", + ((is_testmode) ? "divider" : "calibration"), + RTTC_VAL); + + seq_printf(seq, "alarm_IRQ\t: %s\n", + (RTIS_VAL & RTIS_ALARM) ? "yes" : "no"); + + seq_printf(seq, "ticks_IRQ\t: %s\n", + (RTIS_VAL & RTIS_UPDATE) ? "yes" : "no"); + + return 0; + +} + +/* + * Wrap "rtc_ops" to "file_operations" in common/rtctime.c + */ +static const struct rtc_class_ops wmt_rtc_ops = { + .open = wmt_rtc_open, + .release = wmt_rtc_release, + .ioctl = wmt_rtc_ioctl, + .read_time = wmt_rtc_read_time, + .set_time = wmt_rtc_set_time, + .read_alarm = wmt_rtc_read_alarm, + .set_alarm = wmt_rtc_set_alarm, + .proc = wmt_rtc_proc, +}; + +static int wmt_rtc_probe(struct platform_device *pdev) +{ + struct rtc_device *wmt_rtc; + + if ((rtc.regs->RTTM & RTTM_ENABLE) && (rtc.regs->RTTC == 0)) { + printk(KERN_WARNING "rtc_warn: initializing default clock divider value\n"); + rtc.regs->RTTC = RTC_DEF_DIVIDER; + } + + if ((rtc.regs->RTCC & RTCC_ENA) == 0) { + /* + * Reset RTC alarm settings + */ + rtc.regs->RTAS = 0; + + /* + * Reset RTC test mode. + */ + rtc.regs->RTTM = 0; + + /* + * Disable all RTC control functions. + * Set to 24-hr format and update type to each second. + * Disable sec/min update interrupt. + * Let RTC free run without interrupts. + */ + rtc.regs->RTCC = (RTCC_ENA | RTCC_INTTYPE); + + if (rtc.regs->RTCD == 0) { + while (rtc.regs->RTWS & RTWS_DATESET) + ; + rtc.regs->RTDS = rtc.regs->RTDS ; + } + } + + device_init_wakeup(&pdev->dev, 1); + + wmt_rtc = rtc_device_register(pdev->name, &pdev->dev, &wmt_rtc_ops, THIS_MODULE); + + if (IS_ERR(wmt_rtc)) { +#ifndef CONFIG_SKIP_DRIVER_MSG + printk(KERN_INFO "WMT Real Time Clock driver v" DRIVER_VERSION " initialized failed\n"); +#endif + return PTR_ERR(wmt_rtc); + } + platform_set_drvdata(pdev, wmt_rtc); + +#ifndef CONFIG_SKIP_DRIVER_MSG + printk(KERN_INFO "WMT Real Time Clock driver v" DRIVER_VERSION " initialized ok\n"); +#endif + return 0; +} + +static int wmt_rtc_remove(struct platform_device *pdev) +{ + struct rtc_device *wmt_rtc = platform_get_drvdata(pdev); + + if (wmt_rtc) + { + rtc.regs->RTCC &= ~RTCC_INTENA; + rtc_device_unregister(wmt_rtc); + } + + return 0; +} + +#ifdef CONFIG_PM +static int wmt_rtc_suspend(struct platform_device *pdev, pm_message_t state) +{ +/* + if (device_may_wakeup(&pdev->dev)) + enable_irq_wake(IRQ_RTC_ALARM); +*/ + return 0; +} + +static int wmt_rtc_resume(struct platform_device *pdev) +{ +/* + if (device_may_wakeup(&pdev->dev)) + disable_irq_wake(IRQ_RTC_ALARM); +*/ + return 0; +} +#else +#define wmt_rtc_suspend NULL +#define wmt_rtc_resume NULL +#endif + +/*************************************************************************** + platform device struct define +****************************************************************************/ +static struct platform_device wmt_rtc_device = { + .name = "wmt-rtc", + .id = 0, +}; + +static struct platform_driver wmt_rtc_driver = { + .probe = wmt_rtc_probe, + .remove = wmt_rtc_remove, + .suspend = wmt_rtc_suspend, + .resume = wmt_rtc_resume, + .driver = { + .name = "wmt-rtc", + }, +}; + +static int __init wmt_rtc_init(void) +{ + int ret = 0; + + if (wmt_rtc_on) { + ret = platform_device_register(&wmt_rtc_device); + if(ret) + return ret; + + ret = platform_driver_register(&wmt_rtc_driver); + if(ret) + { + printk("[wmt_rtc_init]Error: Can not register WMT RTC driver\n"); + platform_device_unregister(&wmt_rtc_device); + return ret; + } + } + return ret; +} + +static void __exit wmt_rtc_exit(void) +{ + if (wmt_rtc_on) { + platform_driver_unregister(&wmt_rtc_driver); + platform_device_unregister(&wmt_rtc_device); + } +} + +module_init(wmt_rtc_init); +module_exit(wmt_rtc_exit); + +MODULE_AUTHOR("WonderMedia Technologies, Inc."); +MODULE_DESCRIPTION("WMT [real time clock] driver"); +MODULE_LICENSE("GPL"); |