18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * rtc-st-lpc.c - ST's LPC RTC, powered by the Low Power Timer 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2014 STMicroelectronics Limited 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Author: David Paris <david.paris@st.com> for STMicroelectronics 88c2ecf20Sopenharmony_ci * Lee Jones <lee.jones@linaro.org> for STMicroelectronics 98c2ecf20Sopenharmony_ci * 108c2ecf20Sopenharmony_ci * Based on the original driver written by Stuart Menefy. 118c2ecf20Sopenharmony_ci */ 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <linux/clk.h> 148c2ecf20Sopenharmony_ci#include <linux/delay.h> 158c2ecf20Sopenharmony_ci#include <linux/init.h> 168c2ecf20Sopenharmony_ci#include <linux/io.h> 178c2ecf20Sopenharmony_ci#include <linux/irq.h> 188c2ecf20Sopenharmony_ci#include <linux/kernel.h> 198c2ecf20Sopenharmony_ci#include <linux/module.h> 208c2ecf20Sopenharmony_ci#include <linux/of.h> 218c2ecf20Sopenharmony_ci#include <linux/of_irq.h> 228c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 238c2ecf20Sopenharmony_ci#include <linux/rtc.h> 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci#include <dt-bindings/mfd/st-lpc.h> 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci/* Low Power Timer */ 288c2ecf20Sopenharmony_ci#define LPC_LPT_LSB_OFF 0x400 298c2ecf20Sopenharmony_ci#define LPC_LPT_MSB_OFF 0x404 308c2ecf20Sopenharmony_ci#define LPC_LPT_START_OFF 0x408 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci/* Low Power Alarm */ 338c2ecf20Sopenharmony_ci#define LPC_LPA_LSB_OFF 0x410 348c2ecf20Sopenharmony_ci#define LPC_LPA_MSB_OFF 0x414 358c2ecf20Sopenharmony_ci#define LPC_LPA_START_OFF 0x418 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci/* LPC as WDT */ 388c2ecf20Sopenharmony_ci#define LPC_WDT_OFF 0x510 398c2ecf20Sopenharmony_ci#define LPC_WDT_FLAG_OFF 0x514 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cistruct st_rtc { 428c2ecf20Sopenharmony_ci struct rtc_device *rtc_dev; 438c2ecf20Sopenharmony_ci struct rtc_wkalrm alarm; 448c2ecf20Sopenharmony_ci struct clk *clk; 458c2ecf20Sopenharmony_ci unsigned long clkrate; 468c2ecf20Sopenharmony_ci void __iomem *ioaddr; 478c2ecf20Sopenharmony_ci bool irq_enabled:1; 488c2ecf20Sopenharmony_ci spinlock_t lock; 498c2ecf20Sopenharmony_ci short irq; 508c2ecf20Sopenharmony_ci}; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_cistatic void st_rtc_set_hw_alarm(struct st_rtc *rtc, 538c2ecf20Sopenharmony_ci unsigned long msb, unsigned long lsb) 548c2ecf20Sopenharmony_ci{ 558c2ecf20Sopenharmony_ci unsigned long flags; 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci spin_lock_irqsave(&rtc->lock, flags); 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci writel_relaxed(1, rtc->ioaddr + LPC_WDT_OFF); 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci writel_relaxed(msb, rtc->ioaddr + LPC_LPA_MSB_OFF); 628c2ecf20Sopenharmony_ci writel_relaxed(lsb, rtc->ioaddr + LPC_LPA_LSB_OFF); 638c2ecf20Sopenharmony_ci writel_relaxed(1, rtc->ioaddr + LPC_LPA_START_OFF); 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci writel_relaxed(0, rtc->ioaddr + LPC_WDT_OFF); 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&rtc->lock, flags); 688c2ecf20Sopenharmony_ci} 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_cistatic irqreturn_t st_rtc_handler(int this_irq, void *data) 718c2ecf20Sopenharmony_ci{ 728c2ecf20Sopenharmony_ci struct st_rtc *rtc = (struct st_rtc *)data; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci rtc_update_irq(rtc->rtc_dev, 1, RTC_AF); 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci return IRQ_HANDLED; 778c2ecf20Sopenharmony_ci} 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_cistatic int st_rtc_read_time(struct device *dev, struct rtc_time *tm) 808c2ecf20Sopenharmony_ci{ 818c2ecf20Sopenharmony_ci struct st_rtc *rtc = dev_get_drvdata(dev); 828c2ecf20Sopenharmony_ci unsigned long lpt_lsb, lpt_msb; 838c2ecf20Sopenharmony_ci unsigned long long lpt; 848c2ecf20Sopenharmony_ci unsigned long flags; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci spin_lock_irqsave(&rtc->lock, flags); 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci do { 898c2ecf20Sopenharmony_ci lpt_msb = readl_relaxed(rtc->ioaddr + LPC_LPT_MSB_OFF); 908c2ecf20Sopenharmony_ci lpt_lsb = readl_relaxed(rtc->ioaddr + LPC_LPT_LSB_OFF); 918c2ecf20Sopenharmony_ci } while (readl_relaxed(rtc->ioaddr + LPC_LPT_MSB_OFF) != lpt_msb); 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&rtc->lock, flags); 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci lpt = ((unsigned long long)lpt_msb << 32) | lpt_lsb; 968c2ecf20Sopenharmony_ci do_div(lpt, rtc->clkrate); 978c2ecf20Sopenharmony_ci rtc_time64_to_tm(lpt, tm); 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci return 0; 1008c2ecf20Sopenharmony_ci} 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_cistatic int st_rtc_set_time(struct device *dev, struct rtc_time *tm) 1038c2ecf20Sopenharmony_ci{ 1048c2ecf20Sopenharmony_ci struct st_rtc *rtc = dev_get_drvdata(dev); 1058c2ecf20Sopenharmony_ci unsigned long long lpt, secs; 1068c2ecf20Sopenharmony_ci unsigned long flags; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci secs = rtc_tm_to_time64(tm); 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci lpt = (unsigned long long)secs * rtc->clkrate; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci spin_lock_irqsave(&rtc->lock, flags); 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci writel_relaxed(lpt >> 32, rtc->ioaddr + LPC_LPT_MSB_OFF); 1158c2ecf20Sopenharmony_ci writel_relaxed(lpt, rtc->ioaddr + LPC_LPT_LSB_OFF); 1168c2ecf20Sopenharmony_ci writel_relaxed(1, rtc->ioaddr + LPC_LPT_START_OFF); 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&rtc->lock, flags); 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci return 0; 1218c2ecf20Sopenharmony_ci} 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_cistatic int st_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *wkalrm) 1248c2ecf20Sopenharmony_ci{ 1258c2ecf20Sopenharmony_ci struct st_rtc *rtc = dev_get_drvdata(dev); 1268c2ecf20Sopenharmony_ci unsigned long flags; 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci spin_lock_irqsave(&rtc->lock, flags); 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci memcpy(wkalrm, &rtc->alarm, sizeof(struct rtc_wkalrm)); 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&rtc->lock, flags); 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci return 0; 1358c2ecf20Sopenharmony_ci} 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_cistatic int st_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) 1388c2ecf20Sopenharmony_ci{ 1398c2ecf20Sopenharmony_ci struct st_rtc *rtc = dev_get_drvdata(dev); 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci if (enabled && !rtc->irq_enabled) { 1428c2ecf20Sopenharmony_ci enable_irq(rtc->irq); 1438c2ecf20Sopenharmony_ci rtc->irq_enabled = true; 1448c2ecf20Sopenharmony_ci } else if (!enabled && rtc->irq_enabled) { 1458c2ecf20Sopenharmony_ci disable_irq(rtc->irq); 1468c2ecf20Sopenharmony_ci rtc->irq_enabled = false; 1478c2ecf20Sopenharmony_ci } 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci return 0; 1508c2ecf20Sopenharmony_ci} 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_cistatic int st_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *t) 1538c2ecf20Sopenharmony_ci{ 1548c2ecf20Sopenharmony_ci struct st_rtc *rtc = dev_get_drvdata(dev); 1558c2ecf20Sopenharmony_ci struct rtc_time now; 1568c2ecf20Sopenharmony_ci unsigned long long now_secs; 1578c2ecf20Sopenharmony_ci unsigned long long alarm_secs; 1588c2ecf20Sopenharmony_ci unsigned long long lpa; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci st_rtc_read_time(dev, &now); 1618c2ecf20Sopenharmony_ci now_secs = rtc_tm_to_time64(&now); 1628c2ecf20Sopenharmony_ci alarm_secs = rtc_tm_to_time64(&t->time); 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci memcpy(&rtc->alarm, t, sizeof(struct rtc_wkalrm)); 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci /* Now many secs to fire */ 1678c2ecf20Sopenharmony_ci alarm_secs -= now_secs; 1688c2ecf20Sopenharmony_ci lpa = (unsigned long long)alarm_secs * rtc->clkrate; 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci st_rtc_set_hw_alarm(rtc, lpa >> 32, lpa); 1718c2ecf20Sopenharmony_ci st_rtc_alarm_irq_enable(dev, t->enabled); 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci return 0; 1748c2ecf20Sopenharmony_ci} 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_cistatic const struct rtc_class_ops st_rtc_ops = { 1778c2ecf20Sopenharmony_ci .read_time = st_rtc_read_time, 1788c2ecf20Sopenharmony_ci .set_time = st_rtc_set_time, 1798c2ecf20Sopenharmony_ci .read_alarm = st_rtc_read_alarm, 1808c2ecf20Sopenharmony_ci .set_alarm = st_rtc_set_alarm, 1818c2ecf20Sopenharmony_ci .alarm_irq_enable = st_rtc_alarm_irq_enable, 1828c2ecf20Sopenharmony_ci}; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_cistatic int st_rtc_probe(struct platform_device *pdev) 1858c2ecf20Sopenharmony_ci{ 1868c2ecf20Sopenharmony_ci struct device_node *np = pdev->dev.of_node; 1878c2ecf20Sopenharmony_ci struct st_rtc *rtc; 1888c2ecf20Sopenharmony_ci uint32_t mode; 1898c2ecf20Sopenharmony_ci int ret = 0; 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci ret = of_property_read_u32(np, "st,lpc-mode", &mode); 1928c2ecf20Sopenharmony_ci if (ret) { 1938c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "An LPC mode must be provided\n"); 1948c2ecf20Sopenharmony_ci return -EINVAL; 1958c2ecf20Sopenharmony_ci } 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci /* LPC can either run as a Clocksource or in RTC or WDT mode */ 1988c2ecf20Sopenharmony_ci if (mode != ST_LPC_MODE_RTC) 1998c2ecf20Sopenharmony_ci return -ENODEV; 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci rtc = devm_kzalloc(&pdev->dev, sizeof(struct st_rtc), GFP_KERNEL); 2028c2ecf20Sopenharmony_ci if (!rtc) 2038c2ecf20Sopenharmony_ci return -ENOMEM; 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci rtc->rtc_dev = devm_rtc_allocate_device(&pdev->dev); 2068c2ecf20Sopenharmony_ci if (IS_ERR(rtc->rtc_dev)) 2078c2ecf20Sopenharmony_ci return PTR_ERR(rtc->rtc_dev); 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci spin_lock_init(&rtc->lock); 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci rtc->ioaddr = devm_platform_ioremap_resource(pdev, 0); 2128c2ecf20Sopenharmony_ci if (IS_ERR(rtc->ioaddr)) 2138c2ecf20Sopenharmony_ci return PTR_ERR(rtc->ioaddr); 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci rtc->irq = irq_of_parse_and_map(np, 0); 2168c2ecf20Sopenharmony_ci if (!rtc->irq) { 2178c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "IRQ missing or invalid\n"); 2188c2ecf20Sopenharmony_ci return -EINVAL; 2198c2ecf20Sopenharmony_ci } 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci ret = devm_request_irq(&pdev->dev, rtc->irq, st_rtc_handler, 0, 2228c2ecf20Sopenharmony_ci pdev->name, rtc); 2238c2ecf20Sopenharmony_ci if (ret) { 2248c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Failed to request irq %i\n", rtc->irq); 2258c2ecf20Sopenharmony_ci return ret; 2268c2ecf20Sopenharmony_ci } 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci enable_irq_wake(rtc->irq); 2298c2ecf20Sopenharmony_ci disable_irq(rtc->irq); 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci rtc->clk = devm_clk_get(&pdev->dev, NULL); 2328c2ecf20Sopenharmony_ci if (IS_ERR(rtc->clk)) { 2338c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Unable to request clock\n"); 2348c2ecf20Sopenharmony_ci return PTR_ERR(rtc->clk); 2358c2ecf20Sopenharmony_ci } 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci clk_prepare_enable(rtc->clk); 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci rtc->clkrate = clk_get_rate(rtc->clk); 2408c2ecf20Sopenharmony_ci if (!rtc->clkrate) { 2418c2ecf20Sopenharmony_ci clk_disable_unprepare(rtc->clk); 2428c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Unable to fetch clock rate\n"); 2438c2ecf20Sopenharmony_ci return -EINVAL; 2448c2ecf20Sopenharmony_ci } 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci device_set_wakeup_capable(&pdev->dev, 1); 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, rtc); 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci rtc->rtc_dev->ops = &st_rtc_ops; 2518c2ecf20Sopenharmony_ci rtc->rtc_dev->range_max = U64_MAX; 2528c2ecf20Sopenharmony_ci do_div(rtc->rtc_dev->range_max, rtc->clkrate); 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci ret = rtc_register_device(rtc->rtc_dev); 2558c2ecf20Sopenharmony_ci if (ret) { 2568c2ecf20Sopenharmony_ci clk_disable_unprepare(rtc->clk); 2578c2ecf20Sopenharmony_ci return ret; 2588c2ecf20Sopenharmony_ci } 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci return 0; 2618c2ecf20Sopenharmony_ci} 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 2648c2ecf20Sopenharmony_cistatic int st_rtc_suspend(struct device *dev) 2658c2ecf20Sopenharmony_ci{ 2668c2ecf20Sopenharmony_ci struct st_rtc *rtc = dev_get_drvdata(dev); 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci if (device_may_wakeup(dev)) 2698c2ecf20Sopenharmony_ci return 0; 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci writel_relaxed(1, rtc->ioaddr + LPC_WDT_OFF); 2728c2ecf20Sopenharmony_ci writel_relaxed(0, rtc->ioaddr + LPC_LPA_START_OFF); 2738c2ecf20Sopenharmony_ci writel_relaxed(0, rtc->ioaddr + LPC_WDT_OFF); 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci return 0; 2768c2ecf20Sopenharmony_ci} 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_cistatic int st_rtc_resume(struct device *dev) 2798c2ecf20Sopenharmony_ci{ 2808c2ecf20Sopenharmony_ci struct st_rtc *rtc = dev_get_drvdata(dev); 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_ci rtc_alarm_irq_enable(rtc->rtc_dev, 0); 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_ci /* 2858c2ecf20Sopenharmony_ci * clean 'rtc->alarm' to allow a new 2868c2ecf20Sopenharmony_ci * .set_alarm to the upper RTC layer 2878c2ecf20Sopenharmony_ci */ 2888c2ecf20Sopenharmony_ci memset(&rtc->alarm, 0, sizeof(struct rtc_wkalrm)); 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ci writel_relaxed(0, rtc->ioaddr + LPC_LPA_MSB_OFF); 2918c2ecf20Sopenharmony_ci writel_relaxed(0, rtc->ioaddr + LPC_LPA_LSB_OFF); 2928c2ecf20Sopenharmony_ci writel_relaxed(1, rtc->ioaddr + LPC_WDT_OFF); 2938c2ecf20Sopenharmony_ci writel_relaxed(1, rtc->ioaddr + LPC_LPA_START_OFF); 2948c2ecf20Sopenharmony_ci writel_relaxed(0, rtc->ioaddr + LPC_WDT_OFF); 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_ci return 0; 2978c2ecf20Sopenharmony_ci} 2988c2ecf20Sopenharmony_ci#endif 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(st_rtc_pm_ops, st_rtc_suspend, st_rtc_resume); 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_cistatic const struct of_device_id st_rtc_match[] = { 3038c2ecf20Sopenharmony_ci { .compatible = "st,stih407-lpc" }, 3048c2ecf20Sopenharmony_ci {} 3058c2ecf20Sopenharmony_ci}; 3068c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, st_rtc_match); 3078c2ecf20Sopenharmony_ci 3088c2ecf20Sopenharmony_cistatic struct platform_driver st_rtc_platform_driver = { 3098c2ecf20Sopenharmony_ci .driver = { 3108c2ecf20Sopenharmony_ci .name = "st-lpc-rtc", 3118c2ecf20Sopenharmony_ci .pm = &st_rtc_pm_ops, 3128c2ecf20Sopenharmony_ci .of_match_table = st_rtc_match, 3138c2ecf20Sopenharmony_ci }, 3148c2ecf20Sopenharmony_ci .probe = st_rtc_probe, 3158c2ecf20Sopenharmony_ci}; 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_cimodule_platform_driver(st_rtc_platform_driver); 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("STMicroelectronics LPC RTC driver"); 3208c2ecf20Sopenharmony_ciMODULE_AUTHOR("David Paris <david.paris@st.com>"); 3218c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 322