blob: c8203ae5363e0c6247c898bb86860d66eb82637f [file] [log] [blame]
/*
* drivers/rtc/ambarella_rtc.c
*
* History:
* 2008/04/01 - [Cao Rongrong] Support pause and resume
* 2009/01/22 - [Anthony Ginger] Port to 2.6.28
*
* Copyright (C) 2004-2009, Ambarella, 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/rtc.h>
#include <linux/bcd.h>
#include <linux/clk.h>
#include <asm/uaccess.h>
#include <mach/hardware.h>
#include <plat/rtc.h>
#define AMBRTC_TIME 0
#define AMBRTC_ALARM 1
struct ambarella_rtc {
struct rtc_device *rtc;
void __iomem *reg;
struct device *dev;
/* limitation for old rtc:
* 1. cannot detect power lost
* 2. the msb 2bits are reserved. */
bool is_limited;
int irq;
};
static inline void ambrtc_reset_rtc(struct ambarella_rtc *ambrtc)
{
u32 delay = ambrtc->is_limited ? 3 : 1;
amba_writel(ambrtc->reg + RTC_RESET_OFFSET, 0x01);
msleep(delay);
amba_writel(ambrtc->reg + RTC_RESET_OFFSET, 0x00);
msleep(delay);
}
static int ambrtc_get_alarm_or_time(struct ambarella_rtc *ambrtc,
int time_alarm)
{
u32 reg_offs, val_sec;
if (time_alarm == AMBRTC_TIME)
reg_offs = RTC_CURT_OFFSET;
else
reg_offs = RTC_ALAT_OFFSET;
val_sec = amba_readl(ambrtc->reg + reg_offs);
/* because old rtc cannot use the msb 2bits, we add 0x40000000
* here, this is a pure software workaround. And the result is that
* the time must be started at least from 2004.01.10 13:38:00 */
if (ambrtc->is_limited)
val_sec |= 0x40000000;
return val_sec;
}
static int ambrtc_set_alarm_or_time(struct ambarella_rtc *ambrtc,
int time_alarm, unsigned long secs)
{
u32 time_val, alarm_val;
if (ambrtc->is_limited && secs < 0x40000000) {
dev_err(ambrtc->dev,
"Invalid date[0x%lx](2004.01.10 13:38:00 ~ )\n", secs);
return -EINVAL;
}
if (time_alarm == AMBRTC_TIME) {
time_val = secs;
alarm_val = amba_readl(ambrtc->reg + RTC_ALAT_OFFSET);
} else {
alarm_val = secs;
time_val = amba_readl(ambrtc->reg + RTC_CURT_OFFSET);
// only for wakeup ambarella internal PWC
amba_writel(ambrtc->reg + RTC_PWC_SET_STATUS_OFFSET, 0x28);
}
if (ambrtc->is_limited) {
time_val &= 0x3fffffff;
alarm_val &= 0x3fffffff;
}
amba_writel(ambrtc->reg + RTC_POS0_OFFSET, 0x80);
amba_writel(ambrtc->reg + RTC_POS1_OFFSET, 0x80);
amba_writel(ambrtc->reg + RTC_POS2_OFFSET, 0x80);
/* reset time and alarm to 0 first */
amba_writel(ambrtc->reg + RTC_PWC_ALAT_OFFSET, 0x0);
amba_writel(ambrtc->reg + RTC_PWC_CURT_OFFSET, 0x0);
ambrtc_reset_rtc(ambrtc);
/* now write the required value to time or alarm */
amba_writel(ambrtc->reg + RTC_PWC_CURT_OFFSET, time_val);
amba_writel(ambrtc->reg + RTC_PWC_ALAT_OFFSET, alarm_val);
ambrtc_reset_rtc(ambrtc);
return 0;
}
static int ambrtc_read_time(struct device *dev, struct rtc_time *tm)
{
struct ambarella_rtc *ambrtc;
u32 time_sec;
ambrtc = dev_get_drvdata(dev);
time_sec = ambrtc_get_alarm_or_time(ambrtc, AMBRTC_TIME);
rtc_time_to_tm(time_sec, tm);
return 0;
}
static int ambrtc_set_mmss(struct device *dev, unsigned long secs)
{
struct ambarella_rtc *ambrtc;
ambrtc = dev_get_drvdata(dev);
return ambrtc_set_alarm_or_time(ambrtc, AMBRTC_TIME, secs);
}
static int ambrtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
{
struct ambarella_rtc *ambrtc;
u32 alarm_sec, time_sec;
u32 rtc_status;
ambrtc = dev_get_drvdata(dev);
alarm_sec = ambrtc_get_alarm_or_time(ambrtc, AMBRTC_ALARM);
rtc_time_to_tm(alarm_sec, &alrm->time);
time_sec = ambrtc_get_alarm_or_time(ambrtc, AMBRTC_TIME);
alrm->enabled = alarm_sec > time_sec;
rtc_status = amba_readl(ambrtc->reg + RTC_STATUS_OFFSET);
alrm->pending = !!(rtc_status & RTC_STATUS_ALA_WK);
return 0;
}
static int ambrtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
{
struct ambarella_rtc *ambrtc;
unsigned long alarm_sec;
ambrtc = dev_get_drvdata(dev);
rtc_tm_to_time(&alrm->time, &alarm_sec);
return ambrtc_set_alarm_or_time(ambrtc, AMBRTC_ALARM, alarm_sec);
}
static int ambrtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
{
return 0;
}
static irqreturn_t ambrtc_alarm_irq(int irq, void *dev_id)
{
struct ambarella_rtc *ambrtc = (struct ambarella_rtc *)dev_id;
if(ambrtc->rtc)
rtc_update_irq(ambrtc->rtc, 1, RTC_IRQF | RTC_AF);
return IRQ_HANDLED;
}
static int ambrtc_ioctl(struct device *dev, unsigned int cmd,
unsigned long arg)
{
struct ambarella_rtc *ambrtc;
int lbat, rval = 0;
ambrtc = dev_get_drvdata(dev);
if (ambrtc->is_limited)
return -ENOIOCTLCMD;
switch (cmd) {
case RTC_VL_READ:
lbat = !!amba_readl(ambrtc->reg + RTC_PWC_LBAT_OFFSET);
rval = put_user(lbat, (int __user *)arg);
break;
default:
rval = -ENOIOCTLCMD;
break;
}
return rval;
}
static const struct rtc_class_ops ambarella_rtc_ops = {
.ioctl = ambrtc_ioctl,
.read_time = ambrtc_read_time,
.set_mmss = ambrtc_set_mmss,
.read_alarm = ambrtc_read_alarm,
.set_alarm = ambrtc_set_alarm,
.alarm_irq_enable = ambrtc_alarm_irq_enable,
};
static void ambrtc_check_power_lost(struct ambarella_rtc *ambrtc)
{
u32 status, need_rst, time_sec;
if (ambrtc->is_limited) {
status = amba_readl(ambrtc->reg + RTC_STATUS_OFFSET);
need_rst = !(status & RTC_STATUS_PC_RST);
} else {
status = amba_readl(ambrtc->reg + RTC_PWC_REG_STA_OFFSET);
need_rst = !(status & RTC_PWC_LOSS_MASK);
amba_setbitsl(ambrtc->reg + RTC_PWC_SET_STATUS_OFFSET,
RTC_PWC_LOSS_MASK);
}
if (need_rst) {
dev_warn(ambrtc->dev, "=====RTC ever lost power=====\n");
time_sec = ambrtc->is_limited ? 0x40000000 : 0;
ambrtc_set_alarm_or_time(ambrtc, AMBRTC_TIME, time_sec);
}
}
static int ambrtc_probe(struct platform_device *pdev)
{
struct ambarella_rtc *ambrtc;
struct resource *mem;
void __iomem *reg;
int ret;
unsigned int wakeup_support;
struct device_node *np = pdev->dev.of_node;
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (mem == NULL) {
dev_err(&pdev->dev, "Get mem resource failed!\n");
return -ENXIO;
}
reg = devm_ioremap(&pdev->dev, mem->start, resource_size(mem));
if (!reg) {
dev_err(&pdev->dev, "devm_ioremap() failed\n");
return -ENOMEM;
}
ambrtc = devm_kzalloc(&pdev->dev, sizeof(*ambrtc), GFP_KERNEL);
if (mem == NULL) {
dev_err(&pdev->dev, "no memory!\n");
return -ENOMEM;
}
ambrtc->irq = platform_get_irq(pdev, 0);
if (ambrtc->irq < 0) {
ambrtc->irq = -1;
} else {
ret = devm_request_irq(&pdev->dev, ambrtc->irq, ambrtc_alarm_irq, IRQF_SHARED,
"rtc alarm", ambrtc);
if (ret) {
dev_err(&pdev->dev, "could not request irq %d for rtc alarm\n", ambrtc->irq);
return ret;
}
}
ambrtc->reg = reg;
ambrtc->dev = &pdev->dev;
ambrtc->is_limited = !!of_find_property(pdev->dev.of_node,
"amb,is-limited", NULL);
platform_set_drvdata(pdev, ambrtc);
ambrtc_check_power_lost(ambrtc);
wakeup_support = !!of_get_property(np, "rtc,wakeup", NULL);
if (wakeup_support)
device_set_wakeup_capable(&pdev->dev, 1);
ambrtc->rtc = devm_rtc_device_register(&pdev->dev, "rtc-ambarella",
&ambarella_rtc_ops, THIS_MODULE);
if (IS_ERR(ambrtc->rtc)) {
dev_err(&pdev->dev, "devm_rtc_device_register fail.\n");
return PTR_ERR(ambrtc->rtc);
}
ambrtc->rtc->uie_unsupported = 1;
return 0;
}
static int ambrtc_remove(struct platform_device *pdev)
{
platform_set_drvdata(pdev, NULL);
return 0;
}
static const struct of_device_id ambarella_rtc_dt_ids[] = {
{.compatible = "ambarella,rtc", },
{},
};
MODULE_DEVICE_TABLE(of, ambarella_rtc_dt_ids);
#ifdef CONFIG_PM_SLEEP
static int ambarella_rtc_suspend(struct device *dev)
{
struct ambarella_rtc *ambrtc = dev_get_drvdata(dev);
if (device_may_wakeup(dev)){
if (ambrtc->irq != -1)
enable_irq_wake(ambrtc->irq);
}
return 0;
}
static int ambarella_rtc_resume(struct device *dev)
{
struct ambarella_rtc *ambrtc = dev_get_drvdata(dev);
if (device_may_wakeup(dev)) {
if (ambrtc->irq != -1)
disable_irq_wake(ambrtc->irq);
}
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(ambarella_rtc_pm_ops, ambarella_rtc_suspend, ambarella_rtc_resume);
static struct platform_driver ambarella_rtc_driver = {
.probe = ambrtc_probe,
.remove = ambrtc_remove,
.driver = {
.name = "ambarella-rtc",
.owner = THIS_MODULE,
.of_match_table = ambarella_rtc_dt_ids,
.pm = &ambarella_rtc_pm_ops,
},
};
module_platform_driver(ambarella_rtc_driver);
MODULE_DESCRIPTION("Ambarella Onchip RTC Driver");
MODULE_AUTHOR("Cao Rongrong <rrcao@ambarella.com>");
MODULE_LICENSE("GPL");