-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
yaml --- r: 60855 b: refs/heads/master c: fa04e78 h: refs/heads/master i: 60853: df925ef 60851: 8faa7f6 60847: 056bea3 v: v3
- Loading branch information
Hans-Christian Egtvedt
authored and
Linus Torvalds
committed
Jul 17, 2007
1 parent
00a2a67
commit 6865f99
Showing
4 changed files
with
326 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
--- | ||
refs/heads/master: ac495bf8971f2dc9d401d892849977e86633ced6 | ||
refs/heads/master: fa04e78b2d44cb923177d7e6988ac32639beb2d0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,317 @@ | ||
/* | ||
* An RTC driver for the AVR32 AT32AP700x processor series. | ||
* | ||
* Copyright (C) 2007 Atmel Corporation | ||
* | ||
* This program is free software; you can redistribute it and/or modify it | ||
* under the terms of the GNU General Public License version 2 as published | ||
* by the Free Software Foundation. | ||
*/ | ||
|
||
#include <linux/module.h> | ||
#include <linux/kernel.h> | ||
#include <linux/platform_device.h> | ||
#include <linux/rtc.h> | ||
#include <linux/io.h> | ||
|
||
/* | ||
* This is a bare-bones RTC. It runs during most system sleep states, but has | ||
* no battery backup and gets reset during system restart. It must be | ||
* initialized from an external clock (network, I2C, etc) before it can be of | ||
* much use. | ||
* | ||
* The alarm functionality is limited by the hardware, not supporting | ||
* periodic interrupts. | ||
*/ | ||
|
||
#define RTC_CTRL 0x00 | ||
#define RTC_CTRL_EN 0 | ||
#define RTC_CTRL_PCLR 1 | ||
#define RTC_CTRL_TOPEN 2 | ||
#define RTC_CTRL_PSEL 8 | ||
|
||
#define RTC_VAL 0x04 | ||
|
||
#define RTC_TOP 0x08 | ||
|
||
#define RTC_IER 0x10 | ||
#define RTC_IER_TOPI 0 | ||
|
||
#define RTC_IDR 0x14 | ||
#define RTC_IDR_TOPI 0 | ||
|
||
#define RTC_IMR 0x18 | ||
#define RTC_IMR_TOPI 0 | ||
|
||
#define RTC_ISR 0x1c | ||
#define RTC_ISR_TOPI 0 | ||
|
||
#define RTC_ICR 0x20 | ||
#define RTC_ICR_TOPI 0 | ||
|
||
#define RTC_BIT(name) (1 << RTC_##name) | ||
#define RTC_BF(name, value) ((value) << RTC_##name) | ||
|
||
#define rtc_readl(dev, reg) \ | ||
__raw_readl((dev)->regs + RTC_##reg) | ||
#define rtc_writel(dev, reg, value) \ | ||
__raw_writel((value), (dev)->regs + RTC_##reg) | ||
|
||
struct rtc_at32ap700x { | ||
struct rtc_device *rtc; | ||
void __iomem *regs; | ||
unsigned long alarm_time; | ||
unsigned long irq; | ||
/* Protect against concurrent register access. */ | ||
spinlock_t lock; | ||
}; | ||
|
||
static int at32_rtc_readtime(struct device *dev, struct rtc_time *tm) | ||
{ | ||
struct rtc_at32ap700x *rtc = dev_get_drvdata(dev); | ||
unsigned long now; | ||
|
||
now = rtc_readl(rtc, VAL); | ||
rtc_time_to_tm(now, tm); | ||
|
||
return 0; | ||
} | ||
|
||
static int at32_rtc_settime(struct device *dev, struct rtc_time *tm) | ||
{ | ||
struct rtc_at32ap700x *rtc = dev_get_drvdata(dev); | ||
unsigned long now; | ||
int ret; | ||
|
||
ret = rtc_tm_to_time(tm, &now); | ||
if (ret == 0) | ||
rtc_writel(rtc, VAL, now); | ||
|
||
return ret; | ||
} | ||
|
||
static int at32_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm) | ||
{ | ||
struct rtc_at32ap700x *rtc = dev_get_drvdata(dev); | ||
|
||
rtc_time_to_tm(rtc->alarm_time, &alrm->time); | ||
alrm->pending = rtc_readl(rtc, IMR) & RTC_BIT(IMR_TOPI) ? 1 : 0; | ||
|
||
return 0; | ||
} | ||
|
||
static int at32_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm) | ||
{ | ||
struct rtc_at32ap700x *rtc = dev_get_drvdata(dev); | ||
unsigned long rtc_unix_time; | ||
unsigned long alarm_unix_time; | ||
int ret; | ||
|
||
rtc_unix_time = rtc_readl(rtc, VAL); | ||
|
||
ret = rtc_tm_to_time(&alrm->time, &alarm_unix_time); | ||
if (ret) | ||
return ret; | ||
|
||
if (alarm_unix_time < rtc_unix_time) | ||
return -EINVAL; | ||
|
||
spin_lock_irq(&rtc->lock); | ||
rtc->alarm_time = alarm_unix_time; | ||
rtc_writel(rtc, TOP, rtc->alarm_time); | ||
if (alrm->pending) | ||
rtc_writel(rtc, CTRL, rtc_readl(rtc, CTRL) | ||
| RTC_BIT(CTRL_TOPEN)); | ||
else | ||
rtc_writel(rtc, CTRL, rtc_readl(rtc, CTRL) | ||
& ~RTC_BIT(CTRL_TOPEN)); | ||
spin_unlock_irq(&rtc->lock); | ||
|
||
return ret; | ||
} | ||
|
||
static int at32_rtc_ioctl(struct device *dev, unsigned int cmd, | ||
unsigned long arg) | ||
{ | ||
struct rtc_at32ap700x *rtc = dev_get_drvdata(dev); | ||
int ret = 0; | ||
|
||
spin_lock_irq(&rtc->lock); | ||
|
||
switch (cmd) { | ||
case RTC_AIE_ON: | ||
if (rtc_readl(rtc, VAL) > rtc->alarm_time) { | ||
ret = -EINVAL; | ||
break; | ||
} | ||
rtc_writel(rtc, CTRL, rtc_readl(rtc, CTRL) | ||
| RTC_BIT(CTRL_TOPEN)); | ||
rtc_writel(rtc, ICR, RTC_BIT(ICR_TOPI)); | ||
rtc_writel(rtc, IER, RTC_BIT(IER_TOPI)); | ||
break; | ||
case RTC_AIE_OFF: | ||
rtc_writel(rtc, CTRL, rtc_readl(rtc, CTRL) | ||
& ~RTC_BIT(CTRL_TOPEN)); | ||
rtc_writel(rtc, IDR, RTC_BIT(IDR_TOPI)); | ||
rtc_writel(rtc, ICR, RTC_BIT(ICR_TOPI)); | ||
break; | ||
default: | ||
ret = -ENOIOCTLCMD; | ||
break; | ||
} | ||
|
||
spin_unlock_irq(&rtc->lock); | ||
|
||
return ret; | ||
} | ||
|
||
static irqreturn_t at32_rtc_interrupt(int irq, void *dev_id) | ||
{ | ||
struct rtc_at32ap700x *rtc = (struct rtc_at32ap700x *)dev_id; | ||
unsigned long isr = rtc_readl(rtc, ISR); | ||
unsigned long events = 0; | ||
int ret = IRQ_NONE; | ||
|
||
spin_lock(&rtc->lock); | ||
|
||
if (isr & RTC_BIT(ISR_TOPI)) { | ||
rtc_writel(rtc, ICR, RTC_BIT(ICR_TOPI)); | ||
rtc_writel(rtc, IDR, RTC_BIT(IDR_TOPI)); | ||
rtc_writel(rtc, CTRL, rtc_readl(rtc, CTRL) | ||
& ~RTC_BIT(CTRL_TOPEN)); | ||
rtc_writel(rtc, VAL, rtc->alarm_time); | ||
events = RTC_AF | RTC_IRQF; | ||
rtc_update_irq(rtc->rtc, 1, events); | ||
ret = IRQ_HANDLED; | ||
} | ||
|
||
spin_unlock(&rtc->lock); | ||
|
||
return ret; | ||
} | ||
|
||
static struct rtc_class_ops at32_rtc_ops = { | ||
.ioctl = at32_rtc_ioctl, | ||
.read_time = at32_rtc_readtime, | ||
.set_time = at32_rtc_settime, | ||
.read_alarm = at32_rtc_readalarm, | ||
.set_alarm = at32_rtc_setalarm, | ||
}; | ||
|
||
static int __init at32_rtc_probe(struct platform_device *pdev) | ||
{ | ||
struct resource *regs; | ||
struct rtc_at32ap700x *rtc; | ||
int irq = -1; | ||
int ret; | ||
|
||
rtc = kzalloc(sizeof(struct rtc_at32ap700x), GFP_KERNEL); | ||
if (!rtc) { | ||
dev_dbg(&pdev->dev, "out of memory\n"); | ||
return -ENOMEM; | ||
} | ||
|
||
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
if (!regs) { | ||
dev_dbg(&pdev->dev, "no mmio resource defined\n"); | ||
ret = -ENXIO; | ||
goto out; | ||
} | ||
|
||
irq = platform_get_irq(pdev, 0); | ||
if (irq < 0) { | ||
dev_dbg(&pdev->dev, "could not get irq\n"); | ||
ret = -ENXIO; | ||
goto out; | ||
} | ||
|
||
ret = request_irq(irq, at32_rtc_interrupt, IRQF_SHARED, "rtc", rtc); | ||
if (ret) { | ||
dev_dbg(&pdev->dev, "could not request irq %d\n", irq); | ||
goto out; | ||
} | ||
|
||
rtc->irq = irq; | ||
rtc->regs = ioremap(regs->start, regs->end - regs->start + 1); | ||
if (!rtc->regs) { | ||
ret = -ENOMEM; | ||
dev_dbg(&pdev->dev, "could not map I/O memory\n"); | ||
goto out_free_irq; | ||
} | ||
spin_lock_init(&rtc->lock); | ||
|
||
/* | ||
* Maybe init RTC: count from zero at 1 Hz, disable wrap irq. | ||
* | ||
* Do not reset VAL register, as it can hold an old time | ||
* from last JTAG reset. | ||
*/ | ||
if (!(rtc_readl(rtc, CTRL) & RTC_BIT(CTRL_EN))) { | ||
rtc_writel(rtc, CTRL, RTC_BIT(CTRL_PCLR)); | ||
rtc_writel(rtc, IDR, RTC_BIT(IDR_TOPI)); | ||
rtc_writel(rtc, CTRL, RTC_BF(CTRL_PSEL, 0xe) | ||
| RTC_BIT(CTRL_EN)); | ||
} | ||
|
||
rtc->rtc = rtc_device_register(pdev->name, &pdev->dev, | ||
&at32_rtc_ops, THIS_MODULE); | ||
if (IS_ERR(rtc->rtc)) { | ||
dev_dbg(&pdev->dev, "could not register rtc device\n"); | ||
ret = PTR_ERR(rtc->rtc); | ||
goto out_iounmap; | ||
} | ||
|
||
platform_set_drvdata(pdev, rtc); | ||
|
||
dev_info(&pdev->dev, "Atmel RTC for AT32AP700x at %08lx irq %ld\n", | ||
(unsigned long)rtc->regs, rtc->irq); | ||
|
||
return 0; | ||
|
||
out_iounmap: | ||
iounmap(rtc->regs); | ||
out_free_irq: | ||
free_irq(irq, rtc); | ||
out: | ||
kfree(rtc); | ||
return ret; | ||
} | ||
|
||
static int __exit at32_rtc_remove(struct platform_device *pdev) | ||
{ | ||
struct rtc_at32ap700x *rtc = platform_get_drvdata(pdev); | ||
|
||
free_irq(rtc->irq, rtc); | ||
iounmap(rtc->regs); | ||
rtc_device_unregister(rtc->rtc); | ||
kfree(rtc); | ||
platform_set_drvdata(pdev, NULL); | ||
|
||
return 0; | ||
} | ||
|
||
MODULE_ALIAS("at32ap700x_rtc"); | ||
|
||
static struct platform_driver at32_rtc_driver = { | ||
.remove = __exit_p(at32_rtc_remove), | ||
.driver = { | ||
.name = "at32ap700x_rtc", | ||
.owner = THIS_MODULE, | ||
}, | ||
}; | ||
|
||
static int __init at32_rtc_init(void) | ||
{ | ||
return platform_driver_probe(&at32_rtc_driver, at32_rtc_probe); | ||
} | ||
module_init(at32_rtc_init); | ||
|
||
static void __exit at32_rtc_exit(void) | ||
{ | ||
platform_driver_unregister(&at32_rtc_driver); | ||
} | ||
module_exit(at32_rtc_exit); | ||
|
||
MODULE_AUTHOR("Hans-Christian Egtvedt <hcegtvedt@atmel.com>"); | ||
MODULE_DESCRIPTION("Real time clock for AVR32 AT32AP700x"); | ||
MODULE_LICENSE("GPL"); |