forked from Xilinx/linux-xlnx
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
rtc: add Freescale stmp37xx/378x driver
Add support for RTC on the Freescale STMP37xx/378x platform. Signed-off-by: dmitry pervushin <dpervushin@embeddedalley.com> Signed-off-by: Alessandro Zummo <a.zummo@towertech.it> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
- Loading branch information
Showing
3 changed files
with
315 additions
and
0 deletions.
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
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,304 @@ | ||
/* | ||
* Freescale STMP37XX/STMP378X Real Time Clock driver | ||
* | ||
* Copyright (c) 2007 Sigmatel, Inc. | ||
* Peter Hartley, <peter.hartley@sigmatel.com> | ||
* | ||
* Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. | ||
* Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. | ||
*/ | ||
|
||
/* | ||
* The code contained herein is licensed under the GNU General Public | ||
* License. You may obtain a copy of the GNU General Public License | ||
* Version 2 or later at the following locations: | ||
* | ||
* http://www.opensource.org/licenses/gpl-license.html | ||
* http://www.gnu.org/copyleft/gpl.html | ||
*/ | ||
#include <linux/kernel.h> | ||
#include <linux/module.h> | ||
#include <linux/init.h> | ||
#include <linux/platform_device.h> | ||
#include <linux/interrupt.h> | ||
#include <linux/rtc.h> | ||
|
||
#include <mach/platform.h> | ||
#include <mach/stmp3xxx.h> | ||
#include <mach/regs-rtc.h> | ||
|
||
struct stmp3xxx_rtc_data { | ||
struct rtc_device *rtc; | ||
unsigned irq_count; | ||
void __iomem *io; | ||
int irq_alarm, irq_1msec; | ||
}; | ||
|
||
static void stmp3xxx_wait_time(struct stmp3xxx_rtc_data *rtc_data) | ||
{ | ||
/* | ||
* The datasheet doesn't say which way round the | ||
* NEW_REGS/STALE_REGS bitfields go. In fact it's 0x1=P0, | ||
* 0x2=P1, .., 0x20=P5, 0x40=ALARM, 0x80=SECONDS | ||
*/ | ||
while (__raw_readl(rtc_data->io + HW_RTC_STAT) & | ||
BF(0x80, RTC_STAT_STALE_REGS)) | ||
cpu_relax(); | ||
} | ||
|
||
/* Time read/write */ | ||
static int stmp3xxx_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm) | ||
{ | ||
struct stmp3xxx_rtc_data *rtc_data = dev_get_drvdata(dev); | ||
|
||
stmp3xxx_wait_time(rtc_data); | ||
rtc_time_to_tm(__raw_readl(rtc_data->io + HW_RTC_SECONDS), rtc_tm); | ||
return 0; | ||
} | ||
|
||
static int stmp3xxx_rtc_set_mmss(struct device *dev, unsigned long t) | ||
{ | ||
struct stmp3xxx_rtc_data *rtc_data = dev_get_drvdata(dev); | ||
|
||
__raw_writel(t, rtc_data->io + HW_RTC_SECONDS); | ||
stmp3xxx_wait_time(rtc_data); | ||
return 0; | ||
} | ||
|
||
/* interrupt(s) handler */ | ||
static irqreturn_t stmp3xxx_rtc_interrupt(int irq, void *dev_id) | ||
{ | ||
struct stmp3xxx_rtc_data *rtc_data = dev_get_drvdata(dev_id); | ||
u32 status; | ||
u32 events = 0; | ||
|
||
status = __raw_readl(rtc_data->io + HW_RTC_CTRL) & | ||
(BM_RTC_CTRL_ALARM_IRQ | BM_RTC_CTRL_ONEMSEC_IRQ); | ||
|
||
if (status & BM_RTC_CTRL_ALARM_IRQ) { | ||
stmp3xxx_clearl(BM_RTC_CTRL_ALARM_IRQ, | ||
rtc_data->io + HW_RTC_CTRL); | ||
events |= RTC_AF | RTC_IRQF; | ||
} | ||
|
||
if (status & BM_RTC_CTRL_ONEMSEC_IRQ) { | ||
stmp3xxx_clearl(BM_RTC_CTRL_ONEMSEC_IRQ, | ||
rtc_data->io + HW_RTC_CTRL); | ||
if (++rtc_data->irq_count % 1000 == 0) { | ||
events |= RTC_UF | RTC_IRQF; | ||
rtc_data->irq_count = 0; | ||
} | ||
} | ||
|
||
if (events) | ||
rtc_update_irq(rtc_data->rtc, 1, events); | ||
|
||
return IRQ_HANDLED; | ||
} | ||
|
||
static int stmp3xxx_alarm_irq_enable(struct device *dev, unsigned int enabled) | ||
{ | ||
struct stmp3xxx_rtc_data *rtc_data = dev_get_drvdata(dev); | ||
void __iomem *p = rtc_data->io + HW_RTC_PERSISTENT0, | ||
*ctl = rtc_data->io + HW_RTC_CTRL; | ||
|
||
if (enabled) { | ||
stmp3xxx_setl(BM_RTC_PERSISTENT0_ALARM_EN | | ||
BM_RTC_PERSISTENT0_ALARM_WAKE_EN, p); | ||
stmp3xxx_setl(BM_RTC_CTRL_ALARM_IRQ_EN, ctl); | ||
} else { | ||
stmp3xxx_clearl(BM_RTC_PERSISTENT0_ALARM_EN | | ||
BM_RTC_PERSISTENT0_ALARM_WAKE_EN, p); | ||
stmp3xxx_clearl(BM_RTC_CTRL_ALARM_IRQ_EN, ctl); | ||
} | ||
return 0; | ||
} | ||
|
||
static int stmp3xxx_update_irq_enable(struct device *dev, unsigned int enabled) | ||
{ | ||
struct stmp3xxx_rtc_data *rtc_data = dev_get_drvdata(dev); | ||
|
||
if (enabled) | ||
stmp3xxx_setl(BM_RTC_CTRL_ONEMSEC_IRQ_EN, | ||
rtc_data->io + HW_RTC_CTRL); | ||
else | ||
stmp3xxx_clearl(BM_RTC_CTRL_ONEMSEC_IRQ_EN, | ||
rtc_data->io + HW_RTC_CTRL); | ||
return 0; | ||
} | ||
|
||
static int stmp3xxx_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm) | ||
{ | ||
struct stmp3xxx_rtc_data *rtc_data = dev_get_drvdata(dev); | ||
|
||
rtc_time_to_tm(__raw_readl(rtc_data->io + HW_RTC_ALARM), &alm->time); | ||
return 0; | ||
} | ||
|
||
static int stmp3xxx_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm) | ||
{ | ||
unsigned long t; | ||
struct stmp3xxx_rtc_data *rtc_data = dev_get_drvdata(dev); | ||
|
||
rtc_tm_to_time(&alm->time, &t); | ||
__raw_writel(t, rtc_data->io + HW_RTC_ALARM); | ||
return 0; | ||
} | ||
|
||
static struct rtc_class_ops stmp3xxx_rtc_ops = { | ||
.alarm_irq_enable = | ||
stmp3xxx_alarm_irq_enable, | ||
.update_irq_enable = | ||
stmp3xxx_update_irq_enable, | ||
.read_time = stmp3xxx_rtc_gettime, | ||
.set_mmss = stmp3xxx_rtc_set_mmss, | ||
.read_alarm = stmp3xxx_rtc_read_alarm, | ||
.set_alarm = stmp3xxx_rtc_set_alarm, | ||
}; | ||
|
||
static int stmp3xxx_rtc_remove(struct platform_device *pdev) | ||
{ | ||
struct stmp3xxx_rtc_data *rtc_data = platform_get_drvdata(pdev); | ||
|
||
if (!rtc_data) | ||
return 0; | ||
|
||
stmp3xxx_clearl(BM_RTC_CTRL_ONEMSEC_IRQ_EN | BM_RTC_CTRL_ALARM_IRQ_EN, | ||
rtc_data->io + HW_RTC_CTRL); | ||
free_irq(rtc_data->irq_alarm, &pdev->dev); | ||
free_irq(rtc_data->irq_1msec, &pdev->dev); | ||
rtc_device_unregister(rtc_data->rtc); | ||
iounmap(rtc_data->io); | ||
kfree(rtc_data); | ||
|
||
return 0; | ||
} | ||
|
||
static int stmp3xxx_rtc_probe(struct platform_device *pdev) | ||
{ | ||
struct stmp3xxx_rtc_data *rtc_data; | ||
struct resource *r; | ||
int err; | ||
|
||
rtc_data = kzalloc(sizeof *rtc_data, GFP_KERNEL); | ||
if (!rtc_data) | ||
return -ENOMEM; | ||
|
||
r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
if (!r) { | ||
dev_err(&pdev->dev, "failed to get resource\n"); | ||
err = -ENXIO; | ||
goto out_free; | ||
} | ||
|
||
rtc_data->io = ioremap(r->start, resource_size(r)); | ||
if (!rtc_data->io) { | ||
dev_err(&pdev->dev, "ioremap failed\n"); | ||
err = -EIO; | ||
goto out_free; | ||
} | ||
|
||
rtc_data->irq_alarm = platform_get_irq(pdev, 0); | ||
rtc_data->irq_1msec = platform_get_irq(pdev, 1); | ||
|
||
if (!(__raw_readl(HW_RTC_STAT + rtc_data->io) & | ||
BM_RTC_STAT_RTC_PRESENT)) { | ||
dev_err(&pdev->dev, "no device onboard\n"); | ||
err = -ENODEV; | ||
goto out_remap; | ||
} | ||
|
||
stmp3xxx_reset_block(rtc_data->io, true); | ||
stmp3xxx_clearl(BM_RTC_PERSISTENT0_ALARM_EN | | ||
BM_RTC_PERSISTENT0_ALARM_WAKE_EN | | ||
BM_RTC_PERSISTENT0_ALARM_WAKE, | ||
rtc_data->io + HW_RTC_PERSISTENT0); | ||
rtc_data->rtc = rtc_device_register(pdev->name, &pdev->dev, | ||
&stmp3xxx_rtc_ops, THIS_MODULE); | ||
if (IS_ERR(rtc_data->rtc)) { | ||
err = PTR_ERR(rtc_data->rtc); | ||
goto out_remap; | ||
} | ||
|
||
rtc_data->irq_count = 0; | ||
err = request_irq(rtc_data->irq_alarm, stmp3xxx_rtc_interrupt, | ||
IRQF_DISABLED, "RTC alarm", &pdev->dev); | ||
if (err) { | ||
dev_err(&pdev->dev, "Cannot claim IRQ%d\n", | ||
rtc_data->irq_alarm); | ||
goto out_irq_alarm; | ||
} | ||
err = request_irq(rtc_data->irq_1msec, stmp3xxx_rtc_interrupt, | ||
IRQF_DISABLED, "RTC tick", &pdev->dev); | ||
if (err) { | ||
dev_err(&pdev->dev, "Cannot claim IRQ%d\n", | ||
rtc_data->irq_1msec); | ||
goto out_irq1; | ||
} | ||
|
||
platform_set_drvdata(pdev, rtc_data); | ||
|
||
return 0; | ||
|
||
out_irq1: | ||
free_irq(rtc_data->irq_alarm, &pdev->dev); | ||
out_irq_alarm: | ||
stmp3xxx_clearl(BM_RTC_CTRL_ONEMSEC_IRQ_EN | BM_RTC_CTRL_ALARM_IRQ_EN, | ||
rtc_data->io + HW_RTC_CTRL); | ||
rtc_device_unregister(rtc_data->rtc); | ||
out_remap: | ||
iounmap(rtc_data->io); | ||
out_free: | ||
kfree(rtc_data); | ||
return err; | ||
} | ||
|
||
#ifdef CONFIG_PM | ||
static int stmp3xxx_rtc_suspend(struct platform_device *dev, pm_message_t state) | ||
{ | ||
return 0; | ||
} | ||
|
||
static int stmp3xxx_rtc_resume(struct platform_device *dev) | ||
{ | ||
struct stmp3xxx_rtc_data *rtc_data = platform_get_drvdata(dev); | ||
|
||
stmp3xxx_reset_block(rtc_data->io, true); | ||
stmp3xxx_clearl(BM_RTC_PERSISTENT0_ALARM_EN | | ||
BM_RTC_PERSISTENT0_ALARM_WAKE_EN | | ||
BM_RTC_PERSISTENT0_ALARM_WAKE, | ||
rtc_data->io + HW_RTC_PERSISTENT0); | ||
return 0; | ||
} | ||
#else | ||
#define stmp3xxx_rtc_suspend NULL | ||
#define stmp3xxx_rtc_resume NULL | ||
#endif | ||
|
||
static struct platform_driver stmp3xxx_rtcdrv = { | ||
.probe = stmp3xxx_rtc_probe, | ||
.remove = stmp3xxx_rtc_remove, | ||
.suspend = stmp3xxx_rtc_suspend, | ||
.resume = stmp3xxx_rtc_resume, | ||
.driver = { | ||
.name = "stmp3xxx-rtc", | ||
.owner = THIS_MODULE, | ||
}, | ||
}; | ||
|
||
static int __init stmp3xxx_rtc_init(void) | ||
{ | ||
return platform_driver_register(&stmp3xxx_rtcdrv); | ||
} | ||
|
||
static void __exit stmp3xxx_rtc_exit(void) | ||
{ | ||
platform_driver_unregister(&stmp3xxx_rtcdrv); | ||
} | ||
|
||
module_init(stmp3xxx_rtc_init); | ||
module_exit(stmp3xxx_rtc_exit); | ||
|
||
MODULE_DESCRIPTION("STMP3xxx RTC Driver"); | ||
MODULE_AUTHOR("dmitry pervushin <dpervushin@embeddedalley.com>"); | ||
MODULE_LICENSE("GPL"); |