diff options
Diffstat (limited to 'drivers/rtc/rtc-coh901331.c')
-rw-r--r-- | drivers/rtc/rtc-coh901331.c | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/drivers/rtc/rtc-coh901331.c b/drivers/rtc/rtc-coh901331.c new file mode 100644 index 000000000..da59917c9 --- /dev/null +++ b/drivers/rtc/rtc-coh901331.c @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2007-2009 ST-Ericsson AB + * Real Time Clock interface for ST-Ericsson AB COH 901 331 RTC. + * Author: Linus Walleij <linus.walleij@stericsson.com> + * Based on rtc-pl031.c by Deepak Saxena <dsaxena@plexity.net> + * Copyright 2006 (c) MontaVista Software, Inc. + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/rtc.h> +#include <linux/clk.h> +#include <linux/interrupt.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <linux/slab.h> + +/* + * Registers in the COH 901 331 + */ +/* Alarm value 32bit (R/W) */ +#define COH901331_ALARM 0x00U +/* Used to set current time 32bit (R/W) */ +#define COH901331_SET_TIME 0x04U +/* Indication if current time is valid 32bit (R/-) */ +#define COH901331_VALID 0x08U +/* Read the current time 32bit (R/-) */ +#define COH901331_CUR_TIME 0x0cU +/* Event register for the "alarm" interrupt */ +#define COH901331_IRQ_EVENT 0x10U +/* Mask register for the "alarm" interrupt */ +#define COH901331_IRQ_MASK 0x14U +/* Force register for the "alarm" interrupt */ +#define COH901331_IRQ_FORCE 0x18U + +/* + * Reference to RTC block clock + * Notice that the frequent clk_enable()/clk_disable() on this + * clock is mainly to be able to turn on/off other clocks in the + * hierarchy as needed, the RTC clock is always on anyway. + */ +struct coh901331_port { + struct rtc_device *rtc; + struct clk *clk; + void __iomem *virtbase; + int irq; +#ifdef CONFIG_PM_SLEEP + u32 irqmaskstore; +#endif +}; + +static irqreturn_t coh901331_interrupt(int irq, void *data) +{ + struct coh901331_port *rtap = data; + + clk_enable(rtap->clk); + /* Ack IRQ */ + writel(1, rtap->virtbase + COH901331_IRQ_EVENT); + /* + * Disable the interrupt. This is necessary because + * the RTC lives on a lower-clocked line and will + * not release the IRQ line until after a few (slower) + * clock cycles. The interrupt will be re-enabled when + * a new alarm is set anyway. + */ + writel(0, rtap->virtbase + COH901331_IRQ_MASK); + clk_disable(rtap->clk); + + /* Set alarm flag */ + rtc_update_irq(rtap->rtc, 1, RTC_AF); + + return IRQ_HANDLED; +} + +static int coh901331_read_time(struct device *dev, struct rtc_time *tm) +{ + struct coh901331_port *rtap = dev_get_drvdata(dev); + + clk_enable(rtap->clk); + /* Check if the time is valid */ + if (!readl(rtap->virtbase + COH901331_VALID)) { + clk_disable(rtap->clk); + return -EINVAL; + } + + rtc_time64_to_tm(readl(rtap->virtbase + COH901331_CUR_TIME), tm); + clk_disable(rtap->clk); + return 0; +} + +static int coh901331_set_time(struct device *dev, struct rtc_time *tm) +{ + struct coh901331_port *rtap = dev_get_drvdata(dev); + + clk_enable(rtap->clk); + writel(rtc_tm_to_time64(tm), rtap->virtbase + COH901331_SET_TIME); + clk_disable(rtap->clk); + + return 0; +} + +static int coh901331_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct coh901331_port *rtap = dev_get_drvdata(dev); + + clk_enable(rtap->clk); + rtc_time64_to_tm(readl(rtap->virtbase + COH901331_ALARM), &alarm->time); + alarm->pending = readl(rtap->virtbase + COH901331_IRQ_EVENT) & 1U; + alarm->enabled = readl(rtap->virtbase + COH901331_IRQ_MASK) & 1U; + clk_disable(rtap->clk); + + return 0; +} + +static int coh901331_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct coh901331_port *rtap = dev_get_drvdata(dev); + unsigned long time = rtc_tm_to_time64(&alarm->time); + + clk_enable(rtap->clk); + writel(time, rtap->virtbase + COH901331_ALARM); + writel(alarm->enabled, rtap->virtbase + COH901331_IRQ_MASK); + clk_disable(rtap->clk); + + return 0; +} + +static int coh901331_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct coh901331_port *rtap = dev_get_drvdata(dev); + + clk_enable(rtap->clk); + if (enabled) + writel(1, rtap->virtbase + COH901331_IRQ_MASK); + else + writel(0, rtap->virtbase + COH901331_IRQ_MASK); + clk_disable(rtap->clk); + + return 0; +} + +static const struct rtc_class_ops coh901331_ops = { + .read_time = coh901331_read_time, + .set_time = coh901331_set_time, + .read_alarm = coh901331_read_alarm, + .set_alarm = coh901331_set_alarm, + .alarm_irq_enable = coh901331_alarm_irq_enable, +}; + +static int __exit coh901331_remove(struct platform_device *pdev) +{ + struct coh901331_port *rtap = platform_get_drvdata(pdev); + + if (rtap) + clk_unprepare(rtap->clk); + + return 0; +} + + +static int __init coh901331_probe(struct platform_device *pdev) +{ + int ret; + struct coh901331_port *rtap; + + rtap = devm_kzalloc(&pdev->dev, + sizeof(struct coh901331_port), GFP_KERNEL); + if (!rtap) + return -ENOMEM; + + rtap->virtbase = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(rtap->virtbase)) + return PTR_ERR(rtap->virtbase); + + rtap->irq = platform_get_irq(pdev, 0); + if (devm_request_irq(&pdev->dev, rtap->irq, coh901331_interrupt, 0, + "RTC COH 901 331 Alarm", rtap)) + return -EIO; + + rtap->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(rtap->clk)) { + ret = PTR_ERR(rtap->clk); + dev_err(&pdev->dev, "could not get clock\n"); + return ret; + } + + rtap->rtc = devm_rtc_allocate_device(&pdev->dev); + if (IS_ERR(rtap->rtc)) + return PTR_ERR(rtap->rtc); + + rtap->rtc->ops = &coh901331_ops; + rtap->rtc->range_max = U32_MAX; + + /* We enable/disable the clock only to assure it works */ + ret = clk_prepare_enable(rtap->clk); + if (ret) { + dev_err(&pdev->dev, "could not enable clock\n"); + return ret; + } + clk_disable(rtap->clk); + + platform_set_drvdata(pdev, rtap); + + ret = rtc_register_device(rtap->rtc); + if (ret) + goto out_no_rtc; + + return 0; + + out_no_rtc: + clk_unprepare(rtap->clk); + return ret; +} + +#ifdef CONFIG_PM_SLEEP +static int coh901331_suspend(struct device *dev) +{ + struct coh901331_port *rtap = dev_get_drvdata(dev); + + /* + * If this RTC alarm will be used for waking the system up, + * don't disable it of course. Else we just disable the alarm + * and await suspension. + */ + if (device_may_wakeup(dev)) { + enable_irq_wake(rtap->irq); + } else { + clk_enable(rtap->clk); + rtap->irqmaskstore = readl(rtap->virtbase + COH901331_IRQ_MASK); + writel(0, rtap->virtbase + COH901331_IRQ_MASK); + clk_disable(rtap->clk); + } + clk_unprepare(rtap->clk); + return 0; +} + +static int coh901331_resume(struct device *dev) +{ + int ret; + struct coh901331_port *rtap = dev_get_drvdata(dev); + + ret = clk_prepare(rtap->clk); + if (ret) + return ret; + + if (device_may_wakeup(dev)) { + disable_irq_wake(rtap->irq); + } else { + clk_enable(rtap->clk); + writel(rtap->irqmaskstore, rtap->virtbase + COH901331_IRQ_MASK); + clk_disable(rtap->clk); + } + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(coh901331_pm_ops, coh901331_suspend, coh901331_resume); + +static void coh901331_shutdown(struct platform_device *pdev) +{ + struct coh901331_port *rtap = platform_get_drvdata(pdev); + + clk_enable(rtap->clk); + writel(0, rtap->virtbase + COH901331_IRQ_MASK); + clk_disable_unprepare(rtap->clk); +} + +static const struct of_device_id coh901331_dt_match[] = { + { .compatible = "stericsson,coh901331" }, + {}, +}; +MODULE_DEVICE_TABLE(of, coh901331_dt_match); + +static struct platform_driver coh901331_driver = { + .driver = { + .name = "rtc-coh901331", + .pm = &coh901331_pm_ops, + .of_match_table = coh901331_dt_match, + }, + .remove = __exit_p(coh901331_remove), + .shutdown = coh901331_shutdown, +}; + +module_platform_driver_probe(coh901331_driver, coh901331_probe); + +MODULE_AUTHOR("Linus Walleij <linus.walleij@stericsson.com>"); +MODULE_DESCRIPTION("ST-Ericsson AB COH 901 331 RTC Driver"); +MODULE_LICENSE("GPL"); |