/*++
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 .
WonderMedia Technologies, Inc.
10F, 529, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C.
--*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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");