linux/drivers/rtc/rtc-sh.c
Paul Mundt 0ac554b9be rtc: rtc-sh: Zero out tm value for invalid rtc states.
Follows the changes of some of the other RTC drivers. If the tm
value is bogus, just zero it out. Adds some sanity for RTC_RD_TIME.

Signed-off-by: Paul Mundt <lethal@linux-sh.org>
2007-11-07 20:13:24 +09:00

664 lines
16 KiB
C

/*
* SuperH On-Chip RTC Support
*
* Copyright (C) 2006, 2007 Paul Mundt
* Copyright (C) 2006 Jamie Lenehan
*
* Based on the old arch/sh/kernel/cpu/rtc.c by:
*
* Copyright (C) 2000 Philipp Rumpf <prumpf@tux.org>
* Copyright (C) 1999 Tetsuya Okada & Niibe Yutaka
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/bcd.h>
#include <linux/rtc.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/seq_file.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/io.h>
#include <asm/rtc.h>
#define DRV_NAME "sh-rtc"
#define DRV_VERSION "0.1.3"
#ifdef CONFIG_CPU_SH3
#define rtc_reg_size sizeof(u16)
#define RTC_BIT_INVERTED 0 /* No bug on SH7708, SH7709A */
#define RTC_DEF_CAPABILITIES 0UL
#elif defined(CONFIG_CPU_SH4)
#define rtc_reg_size sizeof(u32)
#define RTC_BIT_INVERTED 0x40 /* bug on SH7750, SH7750S */
#define RTC_DEF_CAPABILITIES RTC_CAP_4_DIGIT_YEAR
#endif
#define RTC_REG(r) ((r) * rtc_reg_size)
#define R64CNT RTC_REG(0)
#define RSECCNT RTC_REG(1) /* RTC sec */
#define RMINCNT RTC_REG(2) /* RTC min */
#define RHRCNT RTC_REG(3) /* RTC hour */
#define RWKCNT RTC_REG(4) /* RTC week */
#define RDAYCNT RTC_REG(5) /* RTC day */
#define RMONCNT RTC_REG(6) /* RTC month */
#define RYRCNT RTC_REG(7) /* RTC year */
#define RSECAR RTC_REG(8) /* ALARM sec */
#define RMINAR RTC_REG(9) /* ALARM min */
#define RHRAR RTC_REG(10) /* ALARM hour */
#define RWKAR RTC_REG(11) /* ALARM week */
#define RDAYAR RTC_REG(12) /* ALARM day */
#define RMONAR RTC_REG(13) /* ALARM month */
#define RCR1 RTC_REG(14) /* Control */
#define RCR2 RTC_REG(15) /* Control */
/* ALARM Bits - or with BCD encoded value */
#define AR_ENB 0x80 /* Enable for alarm cmp */
/* RCR1 Bits */
#define RCR1_CF 0x80 /* Carry Flag */
#define RCR1_CIE 0x10 /* Carry Interrupt Enable */
#define RCR1_AIE 0x08 /* Alarm Interrupt Enable */
#define RCR1_AF 0x01 /* Alarm Flag */
/* RCR2 Bits */
#define RCR2_PEF 0x80 /* PEriodic interrupt Flag */
#define RCR2_PESMASK 0x70 /* Periodic interrupt Set */
#define RCR2_RTCEN 0x08 /* ENable RTC */
#define RCR2_ADJ 0x04 /* ADJustment (30-second) */
#define RCR2_RESET 0x02 /* Reset bit */
#define RCR2_START 0x01 /* Start bit */
struct sh_rtc {
void __iomem *regbase;
unsigned long regsize;
struct resource *res;
unsigned int alarm_irq, periodic_irq, carry_irq;
struct rtc_device *rtc_dev;
spinlock_t lock;
int rearm_aie;
unsigned long capabilities; /* See asm-sh/rtc.h for cap bits */
};
static irqreturn_t sh_rtc_interrupt(int irq, void *dev_id)
{
struct platform_device *pdev = to_platform_device(dev_id);
struct sh_rtc *rtc = platform_get_drvdata(pdev);
unsigned int tmp, events = 0;
spin_lock(&rtc->lock);
tmp = readb(rtc->regbase + RCR1);
tmp &= ~RCR1_CF;
if (rtc->rearm_aie) {
if (tmp & RCR1_AF)
tmp &= ~RCR1_AF; /* try to clear AF again */
else {
tmp |= RCR1_AIE; /* AF has cleared, rearm IRQ */
rtc->rearm_aie = 0;
}
}
writeb(tmp, rtc->regbase + RCR1);
rtc_update_irq(rtc->rtc_dev, 1, events);
spin_unlock(&rtc->lock);
return IRQ_HANDLED;
}
static irqreturn_t sh_rtc_alarm(int irq, void *dev_id)
{
struct platform_device *pdev = to_platform_device(dev_id);
struct sh_rtc *rtc = platform_get_drvdata(pdev);
unsigned int tmp, events = 0;
spin_lock(&rtc->lock);
tmp = readb(rtc->regbase + RCR1);
/*
* If AF is set then the alarm has triggered. If we clear AF while
* the alarm time still matches the RTC time then AF will
* immediately be set again, and if AIE is enabled then the alarm
* interrupt will immediately be retrigger. So we clear AIE here
* and use rtc->rearm_aie so that the carry interrupt will keep
* trying to clear AF and once it stays cleared it'll re-enable
* AIE.
*/
if (tmp & RCR1_AF) {
events |= RTC_AF | RTC_IRQF;
tmp &= ~(RCR1_AF|RCR1_AIE);
writeb(tmp, rtc->regbase + RCR1);
rtc->rearm_aie = 1;
rtc_update_irq(rtc->rtc_dev, 1, events);
}
spin_unlock(&rtc->lock);
return IRQ_HANDLED;
}
static irqreturn_t sh_rtc_periodic(int irq, void *dev_id)
{
struct platform_device *pdev = to_platform_device(dev_id);
struct sh_rtc *rtc = platform_get_drvdata(pdev);
spin_lock(&rtc->lock);
rtc_update_irq(rtc->rtc_dev, 1, RTC_PF | RTC_IRQF);
spin_unlock(&rtc->lock);
return IRQ_HANDLED;
}
static inline void sh_rtc_setpie(struct device *dev, unsigned int enable)
{
struct sh_rtc *rtc = dev_get_drvdata(dev);
unsigned int tmp;
spin_lock_irq(&rtc->lock);
tmp = readb(rtc->regbase + RCR2);
if (enable) {
tmp &= ~RCR2_PESMASK;
tmp |= RCR2_PEF | (2 << 4);
} else
tmp &= ~(RCR2_PESMASK | RCR2_PEF);
writeb(tmp, rtc->regbase + RCR2);
spin_unlock_irq(&rtc->lock);
}
static inline void sh_rtc_setaie(struct device *dev, unsigned int enable)
{
struct sh_rtc *rtc = dev_get_drvdata(dev);
unsigned int tmp;
spin_lock_irq(&rtc->lock);
tmp = readb(rtc->regbase + RCR1);
if (!enable) {
tmp &= ~RCR1_AIE;
rtc->rearm_aie = 0;
} else if (rtc->rearm_aie == 0)
tmp |= RCR1_AIE;
writeb(tmp, rtc->regbase + RCR1);
spin_unlock_irq(&rtc->lock);
}
static int sh_rtc_open(struct device *dev)
{
struct sh_rtc *rtc = dev_get_drvdata(dev);
unsigned int tmp;
int ret;
tmp = readb(rtc->regbase + RCR1);
tmp &= ~RCR1_CF;
tmp |= RCR1_CIE;
writeb(tmp, rtc->regbase + RCR1);
ret = request_irq(rtc->periodic_irq, sh_rtc_periodic, IRQF_DISABLED,
"sh-rtc period", dev);
if (unlikely(ret)) {
dev_err(dev, "request period IRQ failed with %d, IRQ %d\n",
ret, rtc->periodic_irq);
return ret;
}
ret = request_irq(rtc->carry_irq, sh_rtc_interrupt, IRQF_DISABLED,
"sh-rtc carry", dev);
if (unlikely(ret)) {
dev_err(dev, "request carry IRQ failed with %d, IRQ %d\n",
ret, rtc->carry_irq);
free_irq(rtc->periodic_irq, dev);
goto err_bad_carry;
}
ret = request_irq(rtc->alarm_irq, sh_rtc_alarm, IRQF_DISABLED,
"sh-rtc alarm", dev);
if (unlikely(ret)) {
dev_err(dev, "request alarm IRQ failed with %d, IRQ %d\n",
ret, rtc->alarm_irq);
goto err_bad_alarm;
}
return 0;
err_bad_alarm:
free_irq(rtc->carry_irq, dev);
err_bad_carry:
free_irq(rtc->periodic_irq, dev);
return ret;
}
static void sh_rtc_release(struct device *dev)
{
struct sh_rtc *rtc = dev_get_drvdata(dev);
sh_rtc_setpie(dev, 0);
sh_rtc_setaie(dev, 0);
free_irq(rtc->periodic_irq, dev);
free_irq(rtc->carry_irq, dev);
free_irq(rtc->alarm_irq, dev);
}
static int sh_rtc_proc(struct device *dev, struct seq_file *seq)
{
struct sh_rtc *rtc = dev_get_drvdata(dev);
unsigned int tmp;
tmp = readb(rtc->regbase + RCR1);
seq_printf(seq, "carry_IRQ\t: %s\n",
(tmp & RCR1_CIE) ? "yes" : "no");
tmp = readb(rtc->regbase + RCR2);
seq_printf(seq, "periodic_IRQ\t: %s\n",
(tmp & RCR2_PEF) ? "yes" : "no");
return 0;
}
static int sh_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
{
unsigned int ret = -ENOIOCTLCMD;
switch (cmd) {
case RTC_PIE_OFF:
case RTC_PIE_ON:
sh_rtc_setpie(dev, cmd == RTC_PIE_ON);
ret = 0;
break;
case RTC_AIE_OFF:
case RTC_AIE_ON:
sh_rtc_setaie(dev, cmd == RTC_AIE_ON);
ret = 0;
break;
}
return ret;
}
static int sh_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
struct platform_device *pdev = to_platform_device(dev);
struct sh_rtc *rtc = platform_get_drvdata(pdev);
unsigned int sec128, sec2, yr, yr100, cf_bit;
do {
unsigned int tmp;
spin_lock_irq(&rtc->lock);
tmp = readb(rtc->regbase + RCR1);
tmp &= ~RCR1_CF; /* Clear CF-bit */
tmp |= RCR1_CIE;
writeb(tmp, rtc->regbase + RCR1);
sec128 = readb(rtc->regbase + R64CNT);
tm->tm_sec = BCD2BIN(readb(rtc->regbase + RSECCNT));
tm->tm_min = BCD2BIN(readb(rtc->regbase + RMINCNT));
tm->tm_hour = BCD2BIN(readb(rtc->regbase + RHRCNT));
tm->tm_wday = BCD2BIN(readb(rtc->regbase + RWKCNT));
tm->tm_mday = BCD2BIN(readb(rtc->regbase + RDAYCNT));
tm->tm_mon = BCD2BIN(readb(rtc->regbase + RMONCNT)) - 1;
if (rtc->capabilities & RTC_CAP_4_DIGIT_YEAR) {
yr = readw(rtc->regbase + RYRCNT);
yr100 = BCD2BIN(yr >> 8);
yr &= 0xff;
} else {
yr = readb(rtc->regbase + RYRCNT);
yr100 = BCD2BIN((yr == 0x99) ? 0x19 : 0x20);
}
tm->tm_year = (yr100 * 100 + BCD2BIN(yr)) - 1900;
sec2 = readb(rtc->regbase + R64CNT);
cf_bit = readb(rtc->regbase + RCR1) & RCR1_CF;
spin_unlock_irq(&rtc->lock);
} while (cf_bit != 0 || ((sec128 ^ sec2) & RTC_BIT_INVERTED) != 0);
#if RTC_BIT_INVERTED != 0
if ((sec128 & RTC_BIT_INVERTED))
tm->tm_sec--;
#endif
dev_dbg(dev, "%s: tm is secs=%d, mins=%d, hours=%d, "
"mday=%d, mon=%d, year=%d, wday=%d\n",
__FUNCTION__,
tm->tm_sec, tm->tm_min, tm->tm_hour,
tm->tm_mday, tm->tm_mon + 1, tm->tm_year, tm->tm_wday);
if (rtc_valid_tm(tm) < 0) {
dev_err(dev, "invalid date\n");
rtc_time_to_tm(0, tm);
}
return 0;
}
static int sh_rtc_set_time(struct device *dev, struct rtc_time *tm)
{
struct platform_device *pdev = to_platform_device(dev);
struct sh_rtc *rtc = platform_get_drvdata(pdev);
unsigned int tmp;
int year;
spin_lock_irq(&rtc->lock);
/* Reset pre-scaler & stop RTC */
tmp = readb(rtc->regbase + RCR2);
tmp |= RCR2_RESET;
tmp &= ~RCR2_START;
writeb(tmp, rtc->regbase + RCR2);
writeb(BIN2BCD(tm->tm_sec), rtc->regbase + RSECCNT);
writeb(BIN2BCD(tm->tm_min), rtc->regbase + RMINCNT);
writeb(BIN2BCD(tm->tm_hour), rtc->regbase + RHRCNT);
writeb(BIN2BCD(tm->tm_wday), rtc->regbase + RWKCNT);
writeb(BIN2BCD(tm->tm_mday), rtc->regbase + RDAYCNT);
writeb(BIN2BCD(tm->tm_mon + 1), rtc->regbase + RMONCNT);
if (rtc->capabilities & RTC_CAP_4_DIGIT_YEAR) {
year = (BIN2BCD((tm->tm_year + 1900) / 100) << 8) |
BIN2BCD(tm->tm_year % 100);
writew(year, rtc->regbase + RYRCNT);
} else {
year = tm->tm_year % 100;
writeb(BIN2BCD(year), rtc->regbase + RYRCNT);
}
/* Start RTC */
tmp = readb(rtc->regbase + RCR2);
tmp &= ~RCR2_RESET;
tmp |= RCR2_RTCEN | RCR2_START;
writeb(tmp, rtc->regbase + RCR2);
spin_unlock_irq(&rtc->lock);
return 0;
}
static inline int sh_rtc_read_alarm_value(struct sh_rtc *rtc, int reg_off)
{
unsigned int byte;
int value = 0xff; /* return 0xff for ignored values */
byte = readb(rtc->regbase + reg_off);
if (byte & AR_ENB) {
byte &= ~AR_ENB; /* strip the enable bit */
value = BCD2BIN(byte);
}
return value;
}
static int sh_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *wkalrm)
{
struct platform_device *pdev = to_platform_device(dev);
struct sh_rtc *rtc = platform_get_drvdata(pdev);
struct rtc_time* tm = &wkalrm->time;
spin_lock_irq(&rtc->lock);
tm->tm_sec = sh_rtc_read_alarm_value(rtc, RSECAR);
tm->tm_min = sh_rtc_read_alarm_value(rtc, RMINAR);
tm->tm_hour = sh_rtc_read_alarm_value(rtc, RHRAR);
tm->tm_wday = sh_rtc_read_alarm_value(rtc, RWKAR);
tm->tm_mday = sh_rtc_read_alarm_value(rtc, RDAYAR);
tm->tm_mon = sh_rtc_read_alarm_value(rtc, RMONAR);
if (tm->tm_mon > 0)
tm->tm_mon -= 1; /* RTC is 1-12, tm_mon is 0-11 */
tm->tm_year = 0xffff;
wkalrm->enabled = (readb(rtc->regbase + RCR1) & RCR1_AIE) ? 1 : 0;
spin_unlock_irq(&rtc->lock);
return 0;
}
static inline void sh_rtc_write_alarm_value(struct sh_rtc *rtc,
int value, int reg_off)
{
/* < 0 for a value that is ignored */
if (value < 0)
writeb(0, rtc->regbase + reg_off);
else
writeb(BIN2BCD(value) | AR_ENB, rtc->regbase + reg_off);
}
static int sh_rtc_check_alarm(struct rtc_time* tm)
{
/*
* The original rtc says anything > 0xc0 is "don't care" or "match
* all" - most users use 0xff but rtc-dev uses -1 for the same thing.
* The original rtc doesn't support years - some things use -1 and
* some 0xffff. We use -1 to make out tests easier.
*/
if (tm->tm_year == 0xffff)
tm->tm_year = -1;
if (tm->tm_mon >= 0xff)
tm->tm_mon = -1;
if (tm->tm_mday >= 0xff)
tm->tm_mday = -1;
if (tm->tm_wday >= 0xff)
tm->tm_wday = -1;
if (tm->tm_hour >= 0xff)
tm->tm_hour = -1;
if (tm->tm_min >= 0xff)
tm->tm_min = -1;
if (tm->tm_sec >= 0xff)
tm->tm_sec = -1;
if (tm->tm_year > 9999 ||
tm->tm_mon >= 12 ||
tm->tm_mday == 0 || tm->tm_mday >= 32 ||
tm->tm_wday >= 7 ||
tm->tm_hour >= 24 ||
tm->tm_min >= 60 ||
tm->tm_sec >= 60)
return -EINVAL;
return 0;
}
static int sh_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *wkalrm)
{
struct platform_device *pdev = to_platform_device(dev);
struct sh_rtc *rtc = platform_get_drvdata(pdev);
unsigned int rcr1;
struct rtc_time *tm = &wkalrm->time;
int mon, err;
err = sh_rtc_check_alarm(tm);
if (unlikely(err < 0))
return err;
spin_lock_irq(&rtc->lock);
/* disable alarm interrupt and clear the alarm flag */
rcr1 = readb(rtc->regbase + RCR1);
rcr1 &= ~(RCR1_AF|RCR1_AIE);
writeb(rcr1, rtc->regbase + RCR1);
rtc->rearm_aie = 0;
/* set alarm time */
sh_rtc_write_alarm_value(rtc, tm->tm_sec, RSECAR);
sh_rtc_write_alarm_value(rtc, tm->tm_min, RMINAR);
sh_rtc_write_alarm_value(rtc, tm->tm_hour, RHRAR);
sh_rtc_write_alarm_value(rtc, tm->tm_wday, RWKAR);
sh_rtc_write_alarm_value(rtc, tm->tm_mday, RDAYAR);
mon = tm->tm_mon;
if (mon >= 0)
mon += 1;
sh_rtc_write_alarm_value(rtc, mon, RMONAR);
if (wkalrm->enabled) {
rcr1 |= RCR1_AIE;
writeb(rcr1, rtc->regbase + RCR1);
}
spin_unlock_irq(&rtc->lock);
return 0;
}
static struct rtc_class_ops sh_rtc_ops = {
.open = sh_rtc_open,
.release = sh_rtc_release,
.ioctl = sh_rtc_ioctl,
.read_time = sh_rtc_read_time,
.set_time = sh_rtc_set_time,
.read_alarm = sh_rtc_read_alarm,
.set_alarm = sh_rtc_set_alarm,
.proc = sh_rtc_proc,
};
static int __devinit sh_rtc_probe(struct platform_device *pdev)
{
struct sh_rtc *rtc;
struct resource *res;
int ret = -ENOENT;
rtc = kzalloc(sizeof(struct sh_rtc), GFP_KERNEL);
if (unlikely(!rtc))
return -ENOMEM;
spin_lock_init(&rtc->lock);
rtc->periodic_irq = platform_get_irq(pdev, 0);
if (unlikely(rtc->periodic_irq < 0)) {
dev_err(&pdev->dev, "No IRQ for period\n");
goto err_badres;
}
rtc->carry_irq = platform_get_irq(pdev, 1);
if (unlikely(rtc->carry_irq < 0)) {
dev_err(&pdev->dev, "No IRQ for carry\n");
goto err_badres;
}
rtc->alarm_irq = platform_get_irq(pdev, 2);
if (unlikely(rtc->alarm_irq < 0)) {
dev_err(&pdev->dev, "No IRQ for alarm\n");
goto err_badres;
}
res = platform_get_resource(pdev, IORESOURCE_IO, 0);
if (unlikely(res == NULL)) {
dev_err(&pdev->dev, "No IO resource\n");
goto err_badres;
}
rtc->regsize = res->end - res->start + 1;
rtc->res = request_mem_region(res->start, rtc->regsize, pdev->name);
if (unlikely(!rtc->res)) {
ret = -EBUSY;
goto err_badres;
}
rtc->regbase = (void __iomem *)rtc->res->start;
if (unlikely(!rtc->regbase)) {
ret = -EINVAL;
goto err_badmap;
}
rtc->rtc_dev = rtc_device_register("sh", &pdev->dev,
&sh_rtc_ops, THIS_MODULE);
if (IS_ERR(rtc->rtc_dev)) {
ret = PTR_ERR(rtc->rtc_dev);
goto err_badmap;
}
rtc->capabilities = RTC_DEF_CAPABILITIES;
if (pdev->dev.platform_data) {
struct sh_rtc_platform_info *pinfo = pdev->dev.platform_data;
/*
* Some CPUs have special capabilities in addition to the
* default set. Add those in here.
*/
rtc->capabilities |= pinfo->capabilities;
}
platform_set_drvdata(pdev, rtc);
return 0;
err_badmap:
release_resource(rtc->res);
err_badres:
kfree(rtc);
return ret;
}
static int __devexit sh_rtc_remove(struct platform_device *pdev)
{
struct sh_rtc *rtc = platform_get_drvdata(pdev);
if (likely(rtc->rtc_dev))
rtc_device_unregister(rtc->rtc_dev);
sh_rtc_setpie(&pdev->dev, 0);
sh_rtc_setaie(&pdev->dev, 0);
release_resource(rtc->res);
platform_set_drvdata(pdev, NULL);
kfree(rtc);
return 0;
}
static struct platform_driver sh_rtc_platform_driver = {
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
},
.probe = sh_rtc_probe,
.remove = __devexit_p(sh_rtc_remove),
};
static int __init sh_rtc_init(void)
{
return platform_driver_register(&sh_rtc_platform_driver);
}
static void __exit sh_rtc_exit(void)
{
platform_driver_unregister(&sh_rtc_platform_driver);
}
module_init(sh_rtc_init);
module_exit(sh_rtc_exit);
MODULE_DESCRIPTION("SuperH on-chip RTC driver");
MODULE_VERSION(DRV_VERSION);
MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>, Jamie Lenehan <lenehan@twibble.org>");
MODULE_LICENSE("GPL");