18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright © 2014-2017 Broadcom 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/clk.h> 98c2ecf20Sopenharmony_ci#include <linux/device.h> 108c2ecf20Sopenharmony_ci#include <linux/err.h> 118c2ecf20Sopenharmony_ci#include <linux/init.h> 128c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 138c2ecf20Sopenharmony_ci#include <linux/io.h> 148c2ecf20Sopenharmony_ci#include <linux/irqreturn.h> 158c2ecf20Sopenharmony_ci#include <linux/kernel.h> 168c2ecf20Sopenharmony_ci#include <linux/module.h> 178c2ecf20Sopenharmony_ci#include <linux/of.h> 188c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 198c2ecf20Sopenharmony_ci#include <linux/pm.h> 208c2ecf20Sopenharmony_ci#include <linux/pm_wakeup.h> 218c2ecf20Sopenharmony_ci#include <linux/reboot.h> 228c2ecf20Sopenharmony_ci#include <linux/rtc.h> 238c2ecf20Sopenharmony_ci#include <linux/stat.h> 248c2ecf20Sopenharmony_ci#include <linux/suspend.h> 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_cistruct brcmstb_waketmr { 278c2ecf20Sopenharmony_ci struct rtc_device *rtc; 288c2ecf20Sopenharmony_ci struct device *dev; 298c2ecf20Sopenharmony_ci void __iomem *base; 308c2ecf20Sopenharmony_ci int irq; 318c2ecf20Sopenharmony_ci struct notifier_block reboot_notifier; 328c2ecf20Sopenharmony_ci struct clk *clk; 338c2ecf20Sopenharmony_ci u32 rate; 348c2ecf20Sopenharmony_ci}; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci#define BRCMSTB_WKTMR_EVENT 0x00 378c2ecf20Sopenharmony_ci#define BRCMSTB_WKTMR_COUNTER 0x04 388c2ecf20Sopenharmony_ci#define BRCMSTB_WKTMR_ALARM 0x08 398c2ecf20Sopenharmony_ci#define BRCMSTB_WKTMR_PRESCALER 0x0C 408c2ecf20Sopenharmony_ci#define BRCMSTB_WKTMR_PRESCALER_VAL 0x10 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci#define BRCMSTB_WKTMR_DEFAULT_FREQ 27000000 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistatic inline void brcmstb_waketmr_clear_alarm(struct brcmstb_waketmr *timer) 458c2ecf20Sopenharmony_ci{ 468c2ecf20Sopenharmony_ci writel_relaxed(1, timer->base + BRCMSTB_WKTMR_EVENT); 478c2ecf20Sopenharmony_ci (void)readl_relaxed(timer->base + BRCMSTB_WKTMR_EVENT); 488c2ecf20Sopenharmony_ci} 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_cistatic void brcmstb_waketmr_set_alarm(struct brcmstb_waketmr *timer, 518c2ecf20Sopenharmony_ci unsigned int secs) 528c2ecf20Sopenharmony_ci{ 538c2ecf20Sopenharmony_ci brcmstb_waketmr_clear_alarm(timer); 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci /* Make sure we are actually counting in seconds */ 568c2ecf20Sopenharmony_ci writel_relaxed(timer->rate, timer->base + BRCMSTB_WKTMR_PRESCALER); 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci writel_relaxed(secs + 1, timer->base + BRCMSTB_WKTMR_ALARM); 598c2ecf20Sopenharmony_ci} 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_cistatic irqreturn_t brcmstb_waketmr_irq(int irq, void *data) 628c2ecf20Sopenharmony_ci{ 638c2ecf20Sopenharmony_ci struct brcmstb_waketmr *timer = data; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci pm_wakeup_event(timer->dev, 0); 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci return IRQ_HANDLED; 688c2ecf20Sopenharmony_ci} 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_cistruct wktmr_time { 718c2ecf20Sopenharmony_ci u32 sec; 728c2ecf20Sopenharmony_ci u32 pre; 738c2ecf20Sopenharmony_ci}; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_cistatic void wktmr_read(struct brcmstb_waketmr *timer, 768c2ecf20Sopenharmony_ci struct wktmr_time *t) 778c2ecf20Sopenharmony_ci{ 788c2ecf20Sopenharmony_ci u32 tmp; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci do { 818c2ecf20Sopenharmony_ci t->sec = readl_relaxed(timer->base + BRCMSTB_WKTMR_COUNTER); 828c2ecf20Sopenharmony_ci tmp = readl_relaxed(timer->base + BRCMSTB_WKTMR_PRESCALER_VAL); 838c2ecf20Sopenharmony_ci } while (tmp >= timer->rate); 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci t->pre = timer->rate - tmp; 868c2ecf20Sopenharmony_ci} 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_cistatic int brcmstb_waketmr_prepare_suspend(struct brcmstb_waketmr *timer) 898c2ecf20Sopenharmony_ci{ 908c2ecf20Sopenharmony_ci struct device *dev = timer->dev; 918c2ecf20Sopenharmony_ci int ret = 0; 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci if (device_may_wakeup(dev)) { 948c2ecf20Sopenharmony_ci ret = enable_irq_wake(timer->irq); 958c2ecf20Sopenharmony_ci if (ret) { 968c2ecf20Sopenharmony_ci dev_err(dev, "failed to enable wake-up interrupt\n"); 978c2ecf20Sopenharmony_ci return ret; 988c2ecf20Sopenharmony_ci } 998c2ecf20Sopenharmony_ci } 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci return ret; 1028c2ecf20Sopenharmony_ci} 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci/* If enabled as a wakeup-source, arm the timer when powering off */ 1058c2ecf20Sopenharmony_cistatic int brcmstb_waketmr_reboot(struct notifier_block *nb, 1068c2ecf20Sopenharmony_ci unsigned long action, void *data) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci struct brcmstb_waketmr *timer; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci timer = container_of(nb, struct brcmstb_waketmr, reboot_notifier); 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci /* Set timer for cold boot */ 1138c2ecf20Sopenharmony_ci if (action == SYS_POWER_OFF) 1148c2ecf20Sopenharmony_ci brcmstb_waketmr_prepare_suspend(timer); 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci return NOTIFY_DONE; 1178c2ecf20Sopenharmony_ci} 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_cistatic int brcmstb_waketmr_gettime(struct device *dev, 1208c2ecf20Sopenharmony_ci struct rtc_time *tm) 1218c2ecf20Sopenharmony_ci{ 1228c2ecf20Sopenharmony_ci struct brcmstb_waketmr *timer = dev_get_drvdata(dev); 1238c2ecf20Sopenharmony_ci struct wktmr_time now; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci wktmr_read(timer, &now); 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci rtc_time64_to_tm(now.sec, tm); 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci return 0; 1308c2ecf20Sopenharmony_ci} 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_cistatic int brcmstb_waketmr_settime(struct device *dev, 1338c2ecf20Sopenharmony_ci struct rtc_time *tm) 1348c2ecf20Sopenharmony_ci{ 1358c2ecf20Sopenharmony_ci struct brcmstb_waketmr *timer = dev_get_drvdata(dev); 1368c2ecf20Sopenharmony_ci time64_t sec; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci sec = rtc_tm_to_time64(tm); 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci writel_relaxed(sec, timer->base + BRCMSTB_WKTMR_COUNTER); 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci return 0; 1438c2ecf20Sopenharmony_ci} 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_cistatic int brcmstb_waketmr_getalarm(struct device *dev, 1468c2ecf20Sopenharmony_ci struct rtc_wkalrm *alarm) 1478c2ecf20Sopenharmony_ci{ 1488c2ecf20Sopenharmony_ci struct brcmstb_waketmr *timer = dev_get_drvdata(dev); 1498c2ecf20Sopenharmony_ci time64_t sec; 1508c2ecf20Sopenharmony_ci u32 reg; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci sec = readl_relaxed(timer->base + BRCMSTB_WKTMR_ALARM); 1538c2ecf20Sopenharmony_ci if (sec != 0) { 1548c2ecf20Sopenharmony_ci /* Alarm is enabled */ 1558c2ecf20Sopenharmony_ci alarm->enabled = 1; 1568c2ecf20Sopenharmony_ci rtc_time64_to_tm(sec, &alarm->time); 1578c2ecf20Sopenharmony_ci } 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci reg = readl_relaxed(timer->base + BRCMSTB_WKTMR_EVENT); 1608c2ecf20Sopenharmony_ci alarm->pending = !!(reg & 1); 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci return 0; 1638c2ecf20Sopenharmony_ci} 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_cistatic int brcmstb_waketmr_setalarm(struct device *dev, 1668c2ecf20Sopenharmony_ci struct rtc_wkalrm *alarm) 1678c2ecf20Sopenharmony_ci{ 1688c2ecf20Sopenharmony_ci struct brcmstb_waketmr *timer = dev_get_drvdata(dev); 1698c2ecf20Sopenharmony_ci time64_t sec; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci if (alarm->enabled) 1728c2ecf20Sopenharmony_ci sec = rtc_tm_to_time64(&alarm->time); 1738c2ecf20Sopenharmony_ci else 1748c2ecf20Sopenharmony_ci sec = 0; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci brcmstb_waketmr_set_alarm(timer, sec); 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci return 0; 1798c2ecf20Sopenharmony_ci} 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci/* 1828c2ecf20Sopenharmony_ci * Does not do much but keep the RTC class happy. We always support 1838c2ecf20Sopenharmony_ci * alarms. 1848c2ecf20Sopenharmony_ci */ 1858c2ecf20Sopenharmony_cistatic int brcmstb_waketmr_alarm_enable(struct device *dev, 1868c2ecf20Sopenharmony_ci unsigned int enabled) 1878c2ecf20Sopenharmony_ci{ 1888c2ecf20Sopenharmony_ci return 0; 1898c2ecf20Sopenharmony_ci} 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_cistatic const struct rtc_class_ops brcmstb_waketmr_ops = { 1928c2ecf20Sopenharmony_ci .read_time = brcmstb_waketmr_gettime, 1938c2ecf20Sopenharmony_ci .set_time = brcmstb_waketmr_settime, 1948c2ecf20Sopenharmony_ci .read_alarm = brcmstb_waketmr_getalarm, 1958c2ecf20Sopenharmony_ci .set_alarm = brcmstb_waketmr_setalarm, 1968c2ecf20Sopenharmony_ci .alarm_irq_enable = brcmstb_waketmr_alarm_enable, 1978c2ecf20Sopenharmony_ci}; 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_cistatic int brcmstb_waketmr_probe(struct platform_device *pdev) 2008c2ecf20Sopenharmony_ci{ 2018c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 2028c2ecf20Sopenharmony_ci struct brcmstb_waketmr *timer; 2038c2ecf20Sopenharmony_ci int ret; 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci timer = devm_kzalloc(dev, sizeof(*timer), GFP_KERNEL); 2068c2ecf20Sopenharmony_ci if (!timer) 2078c2ecf20Sopenharmony_ci return -ENOMEM; 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, timer); 2108c2ecf20Sopenharmony_ci timer->dev = dev; 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci timer->base = devm_platform_ioremap_resource(pdev, 0); 2138c2ecf20Sopenharmony_ci if (IS_ERR(timer->base)) 2148c2ecf20Sopenharmony_ci return PTR_ERR(timer->base); 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci timer->rtc = devm_rtc_allocate_device(dev); 2178c2ecf20Sopenharmony_ci if (IS_ERR(timer->rtc)) 2188c2ecf20Sopenharmony_ci return PTR_ERR(timer->rtc); 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci /* 2218c2ecf20Sopenharmony_ci * Set wakeup capability before requesting wakeup interrupt, so we can 2228c2ecf20Sopenharmony_ci * process boot-time "wakeups" (e.g., from S5 soft-off) 2238c2ecf20Sopenharmony_ci */ 2248c2ecf20Sopenharmony_ci device_set_wakeup_capable(dev, true); 2258c2ecf20Sopenharmony_ci device_wakeup_enable(dev); 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci timer->irq = platform_get_irq(pdev, 0); 2288c2ecf20Sopenharmony_ci if (timer->irq < 0) 2298c2ecf20Sopenharmony_ci return -ENODEV; 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci timer->clk = devm_clk_get(dev, NULL); 2328c2ecf20Sopenharmony_ci if (!IS_ERR(timer->clk)) { 2338c2ecf20Sopenharmony_ci ret = clk_prepare_enable(timer->clk); 2348c2ecf20Sopenharmony_ci if (ret) 2358c2ecf20Sopenharmony_ci return ret; 2368c2ecf20Sopenharmony_ci timer->rate = clk_get_rate(timer->clk); 2378c2ecf20Sopenharmony_ci if (!timer->rate) 2388c2ecf20Sopenharmony_ci timer->rate = BRCMSTB_WKTMR_DEFAULT_FREQ; 2398c2ecf20Sopenharmony_ci } else { 2408c2ecf20Sopenharmony_ci timer->rate = BRCMSTB_WKTMR_DEFAULT_FREQ; 2418c2ecf20Sopenharmony_ci timer->clk = NULL; 2428c2ecf20Sopenharmony_ci } 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci ret = devm_request_irq(dev, timer->irq, brcmstb_waketmr_irq, 0, 2458c2ecf20Sopenharmony_ci "brcmstb-waketimer", timer); 2468c2ecf20Sopenharmony_ci if (ret < 0) 2478c2ecf20Sopenharmony_ci goto err_clk; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci timer->reboot_notifier.notifier_call = brcmstb_waketmr_reboot; 2508c2ecf20Sopenharmony_ci register_reboot_notifier(&timer->reboot_notifier); 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci timer->rtc->ops = &brcmstb_waketmr_ops; 2538c2ecf20Sopenharmony_ci timer->rtc->range_max = U32_MAX; 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci ret = rtc_register_device(timer->rtc); 2568c2ecf20Sopenharmony_ci if (ret) 2578c2ecf20Sopenharmony_ci goto err_notifier; 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci dev_info(dev, "registered, with irq %d\n", timer->irq); 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci return 0; 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_cierr_notifier: 2648c2ecf20Sopenharmony_ci unregister_reboot_notifier(&timer->reboot_notifier); 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_cierr_clk: 2678c2ecf20Sopenharmony_ci if (timer->clk) 2688c2ecf20Sopenharmony_ci clk_disable_unprepare(timer->clk); 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci return ret; 2718c2ecf20Sopenharmony_ci} 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_cistatic int brcmstb_waketmr_remove(struct platform_device *pdev) 2748c2ecf20Sopenharmony_ci{ 2758c2ecf20Sopenharmony_ci struct brcmstb_waketmr *timer = dev_get_drvdata(&pdev->dev); 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ci unregister_reboot_notifier(&timer->reboot_notifier); 2788c2ecf20Sopenharmony_ci clk_disable_unprepare(timer->clk); 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ci return 0; 2818c2ecf20Sopenharmony_ci} 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 2848c2ecf20Sopenharmony_cistatic int brcmstb_waketmr_suspend(struct device *dev) 2858c2ecf20Sopenharmony_ci{ 2868c2ecf20Sopenharmony_ci struct brcmstb_waketmr *timer = dev_get_drvdata(dev); 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci return brcmstb_waketmr_prepare_suspend(timer); 2898c2ecf20Sopenharmony_ci} 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_cistatic int brcmstb_waketmr_resume(struct device *dev) 2928c2ecf20Sopenharmony_ci{ 2938c2ecf20Sopenharmony_ci struct brcmstb_waketmr *timer = dev_get_drvdata(dev); 2948c2ecf20Sopenharmony_ci int ret; 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_ci if (!device_may_wakeup(dev)) 2978c2ecf20Sopenharmony_ci return 0; 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_ci ret = disable_irq_wake(timer->irq); 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_ci brcmstb_waketmr_clear_alarm(timer); 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ci return ret; 3048c2ecf20Sopenharmony_ci} 3058c2ecf20Sopenharmony_ci#endif /* CONFIG_PM_SLEEP */ 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(brcmstb_waketmr_pm_ops, 3088c2ecf20Sopenharmony_ci brcmstb_waketmr_suspend, brcmstb_waketmr_resume); 3098c2ecf20Sopenharmony_ci 3108c2ecf20Sopenharmony_cistatic const struct of_device_id brcmstb_waketmr_of_match[] = { 3118c2ecf20Sopenharmony_ci { .compatible = "brcm,brcmstb-waketimer" }, 3128c2ecf20Sopenharmony_ci { /* sentinel */ }, 3138c2ecf20Sopenharmony_ci}; 3148c2ecf20Sopenharmony_ci 3158c2ecf20Sopenharmony_cistatic struct platform_driver brcmstb_waketmr_driver = { 3168c2ecf20Sopenharmony_ci .probe = brcmstb_waketmr_probe, 3178c2ecf20Sopenharmony_ci .remove = brcmstb_waketmr_remove, 3188c2ecf20Sopenharmony_ci .driver = { 3198c2ecf20Sopenharmony_ci .name = "brcmstb-waketimer", 3208c2ecf20Sopenharmony_ci .pm = &brcmstb_waketmr_pm_ops, 3218c2ecf20Sopenharmony_ci .of_match_table = of_match_ptr(brcmstb_waketmr_of_match), 3228c2ecf20Sopenharmony_ci } 3238c2ecf20Sopenharmony_ci}; 3248c2ecf20Sopenharmony_cimodule_platform_driver(brcmstb_waketmr_driver); 3258c2ecf20Sopenharmony_ci 3268c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 3278c2ecf20Sopenharmony_ciMODULE_AUTHOR("Brian Norris"); 3288c2ecf20Sopenharmony_ciMODULE_AUTHOR("Markus Mayer"); 3298c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Wake-up timer driver for STB chips"); 330