11802d0beSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 2c4f07eceSBrian Norris /* 3c4f07eceSBrian Norris * Copyright © 2014-2017 Broadcom 4c4f07eceSBrian Norris */ 5c4f07eceSBrian Norris 6c4f07eceSBrian Norris #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 7c4f07eceSBrian Norris 8c4f07eceSBrian Norris #include <linux/clk.h> 9c4f07eceSBrian Norris #include <linux/device.h> 10c4f07eceSBrian Norris #include <linux/err.h> 11c4f07eceSBrian Norris #include <linux/init.h> 12c4f07eceSBrian Norris #include <linux/interrupt.h> 13c4f07eceSBrian Norris #include <linux/io.h> 14c4f07eceSBrian Norris #include <linux/irqreturn.h> 15c4f07eceSBrian Norris #include <linux/kernel.h> 16c4f07eceSBrian Norris #include <linux/module.h> 17c4f07eceSBrian Norris #include <linux/of.h> 18c4f07eceSBrian Norris #include <linux/platform_device.h> 19c4f07eceSBrian Norris #include <linux/pm.h> 20c4f07eceSBrian Norris #include <linux/pm_wakeup.h> 21c4f07eceSBrian Norris #include <linux/reboot.h> 22c4f07eceSBrian Norris #include <linux/rtc.h> 23c4f07eceSBrian Norris #include <linux/stat.h> 24c4f07eceSBrian Norris #include <linux/suspend.h> 25c4f07eceSBrian Norris 26c4f07eceSBrian Norris struct brcmstb_waketmr { 27c4f07eceSBrian Norris struct rtc_device *rtc; 28c4f07eceSBrian Norris struct device *dev; 29c4f07eceSBrian Norris void __iomem *base; 30c4f07eceSBrian Norris int irq; 31c4f07eceSBrian Norris struct notifier_block reboot_notifier; 32c4f07eceSBrian Norris struct clk *clk; 33c4f07eceSBrian Norris u32 rate; 34c4f07eceSBrian Norris }; 35c4f07eceSBrian Norris 36c4f07eceSBrian Norris #define BRCMSTB_WKTMR_EVENT 0x00 3790226f6bSDoug Berger #define WKTMR_ALARM_EVENT BIT(0) 38c4f07eceSBrian Norris #define BRCMSTB_WKTMR_COUNTER 0x04 39c4f07eceSBrian Norris #define BRCMSTB_WKTMR_ALARM 0x08 40c4f07eceSBrian Norris #define BRCMSTB_WKTMR_PRESCALER 0x0C 41c4f07eceSBrian Norris #define BRCMSTB_WKTMR_PRESCALER_VAL 0x10 42c4f07eceSBrian Norris 43c4f07eceSBrian Norris #define BRCMSTB_WKTMR_DEFAULT_FREQ 27000000 44c4f07eceSBrian Norris 4590226f6bSDoug Berger static inline bool brcmstb_waketmr_is_pending(struct brcmstb_waketmr *timer) 4690226f6bSDoug Berger { 4790226f6bSDoug Berger u32 reg; 4890226f6bSDoug Berger 4990226f6bSDoug Berger reg = readl_relaxed(timer->base + BRCMSTB_WKTMR_EVENT); 5090226f6bSDoug Berger return !!(reg & WKTMR_ALARM_EVENT); 5190226f6bSDoug Berger } 5290226f6bSDoug Berger 53c4f07eceSBrian Norris static inline void brcmstb_waketmr_clear_alarm(struct brcmstb_waketmr *timer) 54c4f07eceSBrian Norris { 5590226f6bSDoug Berger writel_relaxed(WKTMR_ALARM_EVENT, timer->base + BRCMSTB_WKTMR_EVENT); 56c4f07eceSBrian Norris (void)readl_relaxed(timer->base + BRCMSTB_WKTMR_EVENT); 57c4f07eceSBrian Norris } 58c4f07eceSBrian Norris 59c4f07eceSBrian Norris static void brcmstb_waketmr_set_alarm(struct brcmstb_waketmr *timer, 60c4f07eceSBrian Norris unsigned int secs) 61c4f07eceSBrian Norris { 62c4f07eceSBrian Norris brcmstb_waketmr_clear_alarm(timer); 63c4f07eceSBrian Norris 6458d3d5e7SJustin Chen /* Make sure we are actually counting in seconds */ 6558d3d5e7SJustin Chen writel_relaxed(timer->rate, timer->base + BRCMSTB_WKTMR_PRESCALER); 6658d3d5e7SJustin Chen 67c4f07eceSBrian Norris writel_relaxed(secs + 1, timer->base + BRCMSTB_WKTMR_ALARM); 68c4f07eceSBrian Norris } 69c4f07eceSBrian Norris 70c4f07eceSBrian Norris static irqreturn_t brcmstb_waketmr_irq(int irq, void *data) 71c4f07eceSBrian Norris { 72c4f07eceSBrian Norris struct brcmstb_waketmr *timer = data; 73c4f07eceSBrian Norris 74c4f07eceSBrian Norris pm_wakeup_event(timer->dev, 0); 75c4f07eceSBrian Norris 76c4f07eceSBrian Norris return IRQ_HANDLED; 77c4f07eceSBrian Norris } 78c4f07eceSBrian Norris 79c4f07eceSBrian Norris struct wktmr_time { 80c4f07eceSBrian Norris u32 sec; 81c4f07eceSBrian Norris u32 pre; 82c4f07eceSBrian Norris }; 83c4f07eceSBrian Norris 84c4f07eceSBrian Norris static void wktmr_read(struct brcmstb_waketmr *timer, 85c4f07eceSBrian Norris struct wktmr_time *t) 86c4f07eceSBrian Norris { 87c4f07eceSBrian Norris u32 tmp; 88c4f07eceSBrian Norris 89c4f07eceSBrian Norris do { 90c4f07eceSBrian Norris t->sec = readl_relaxed(timer->base + BRCMSTB_WKTMR_COUNTER); 91c4f07eceSBrian Norris tmp = readl_relaxed(timer->base + BRCMSTB_WKTMR_PRESCALER_VAL); 92c4f07eceSBrian Norris } while (tmp >= timer->rate); 93c4f07eceSBrian Norris 94c4f07eceSBrian Norris t->pre = timer->rate - tmp; 95c4f07eceSBrian Norris } 96c4f07eceSBrian Norris 97c4f07eceSBrian Norris static int brcmstb_waketmr_prepare_suspend(struct brcmstb_waketmr *timer) 98c4f07eceSBrian Norris { 99c4f07eceSBrian Norris struct device *dev = timer->dev; 100c4f07eceSBrian Norris int ret = 0; 101c4f07eceSBrian Norris 102c4f07eceSBrian Norris if (device_may_wakeup(dev)) { 103c4f07eceSBrian Norris ret = enable_irq_wake(timer->irq); 104c4f07eceSBrian Norris if (ret) { 105c4f07eceSBrian Norris dev_err(dev, "failed to enable wake-up interrupt\n"); 106c4f07eceSBrian Norris return ret; 107c4f07eceSBrian Norris } 108c4f07eceSBrian Norris } 109c4f07eceSBrian Norris 110c4f07eceSBrian Norris return ret; 111c4f07eceSBrian Norris } 112c4f07eceSBrian Norris 113c4f07eceSBrian Norris /* If enabled as a wakeup-source, arm the timer when powering off */ 114c4f07eceSBrian Norris static int brcmstb_waketmr_reboot(struct notifier_block *nb, 115c4f07eceSBrian Norris unsigned long action, void *data) 116c4f07eceSBrian Norris { 117c4f07eceSBrian Norris struct brcmstb_waketmr *timer; 118c4f07eceSBrian Norris 119c4f07eceSBrian Norris timer = container_of(nb, struct brcmstb_waketmr, reboot_notifier); 120c4f07eceSBrian Norris 121c4f07eceSBrian Norris /* Set timer for cold boot */ 122c4f07eceSBrian Norris if (action == SYS_POWER_OFF) 123c4f07eceSBrian Norris brcmstb_waketmr_prepare_suspend(timer); 124c4f07eceSBrian Norris 125c4f07eceSBrian Norris return NOTIFY_DONE; 126c4f07eceSBrian Norris } 127c4f07eceSBrian Norris 128c4f07eceSBrian Norris static int brcmstb_waketmr_gettime(struct device *dev, 129c4f07eceSBrian Norris struct rtc_time *tm) 130c4f07eceSBrian Norris { 131c4f07eceSBrian Norris struct brcmstb_waketmr *timer = dev_get_drvdata(dev); 132c4f07eceSBrian Norris struct wktmr_time now; 133c4f07eceSBrian Norris 134c4f07eceSBrian Norris wktmr_read(timer, &now); 135c4f07eceSBrian Norris 1369a8f2d12SAlexandre Belloni rtc_time64_to_tm(now.sec, tm); 137c4f07eceSBrian Norris 138c4f07eceSBrian Norris return 0; 139c4f07eceSBrian Norris } 140c4f07eceSBrian Norris 141c4f07eceSBrian Norris static int brcmstb_waketmr_settime(struct device *dev, 142c4f07eceSBrian Norris struct rtc_time *tm) 143c4f07eceSBrian Norris { 144c4f07eceSBrian Norris struct brcmstb_waketmr *timer = dev_get_drvdata(dev); 145c4f07eceSBrian Norris time64_t sec; 146c4f07eceSBrian Norris 147c4f07eceSBrian Norris sec = rtc_tm_to_time64(tm); 148c4f07eceSBrian Norris 149c4f07eceSBrian Norris writel_relaxed(sec, timer->base + BRCMSTB_WKTMR_COUNTER); 150c4f07eceSBrian Norris 151c4f07eceSBrian Norris return 0; 152c4f07eceSBrian Norris } 153c4f07eceSBrian Norris 154c4f07eceSBrian Norris static int brcmstb_waketmr_getalarm(struct device *dev, 155c4f07eceSBrian Norris struct rtc_wkalrm *alarm) 156c4f07eceSBrian Norris { 157c4f07eceSBrian Norris struct brcmstb_waketmr *timer = dev_get_drvdata(dev); 158c4f07eceSBrian Norris time64_t sec; 159c4f07eceSBrian Norris 160c4f07eceSBrian Norris sec = readl_relaxed(timer->base + BRCMSTB_WKTMR_ALARM); 161c4f07eceSBrian Norris if (sec != 0) { 162c4f07eceSBrian Norris /* Alarm is enabled */ 163c4f07eceSBrian Norris alarm->enabled = 1; 164c4f07eceSBrian Norris rtc_time64_to_tm(sec, &alarm->time); 165c4f07eceSBrian Norris } 166c4f07eceSBrian Norris 16790226f6bSDoug Berger alarm->pending = brcmstb_waketmr_is_pending(timer); 168c4f07eceSBrian Norris 169c4f07eceSBrian Norris return 0; 170c4f07eceSBrian Norris } 171c4f07eceSBrian Norris 172*2cd98b22SDoug Berger /* 173*2cd98b22SDoug Berger * Does not do much but keep the RTC class happy. We always support 174*2cd98b22SDoug Berger * alarms. 175*2cd98b22SDoug Berger */ 176*2cd98b22SDoug Berger static int brcmstb_waketmr_alarm_enable(struct device *dev, 177*2cd98b22SDoug Berger unsigned int enabled) 178*2cd98b22SDoug Berger { 179*2cd98b22SDoug Berger return 0; 180*2cd98b22SDoug Berger } 181*2cd98b22SDoug Berger 182c4f07eceSBrian Norris static int brcmstb_waketmr_setalarm(struct device *dev, 183c4f07eceSBrian Norris struct rtc_wkalrm *alarm) 184c4f07eceSBrian Norris { 185c4f07eceSBrian Norris struct brcmstb_waketmr *timer = dev_get_drvdata(dev); 186c4f07eceSBrian Norris time64_t sec; 187c4f07eceSBrian Norris 188c4f07eceSBrian Norris if (alarm->enabled) 189c4f07eceSBrian Norris sec = rtc_tm_to_time64(&alarm->time); 190c4f07eceSBrian Norris else 191c4f07eceSBrian Norris sec = 0; 192c4f07eceSBrian Norris 193c4f07eceSBrian Norris brcmstb_waketmr_set_alarm(timer, sec); 194c4f07eceSBrian Norris 195*2cd98b22SDoug Berger return brcmstb_waketmr_alarm_enable(dev, alarm->enabled); 196c4f07eceSBrian Norris } 197c4f07eceSBrian Norris 198c4f07eceSBrian Norris static const struct rtc_class_ops brcmstb_waketmr_ops = { 199c4f07eceSBrian Norris .read_time = brcmstb_waketmr_gettime, 200c4f07eceSBrian Norris .set_time = brcmstb_waketmr_settime, 201c4f07eceSBrian Norris .read_alarm = brcmstb_waketmr_getalarm, 202c4f07eceSBrian Norris .set_alarm = brcmstb_waketmr_setalarm, 203c4f07eceSBrian Norris .alarm_irq_enable = brcmstb_waketmr_alarm_enable, 204c4f07eceSBrian Norris }; 205c4f07eceSBrian Norris 206c4f07eceSBrian Norris static int brcmstb_waketmr_probe(struct platform_device *pdev) 207c4f07eceSBrian Norris { 208c4f07eceSBrian Norris struct device *dev = &pdev->dev; 209c4f07eceSBrian Norris struct brcmstb_waketmr *timer; 210c4f07eceSBrian Norris int ret; 211c4f07eceSBrian Norris 212c4f07eceSBrian Norris timer = devm_kzalloc(dev, sizeof(*timer), GFP_KERNEL); 213c4f07eceSBrian Norris if (!timer) 214c4f07eceSBrian Norris return -ENOMEM; 215c4f07eceSBrian Norris 216c4f07eceSBrian Norris platform_set_drvdata(pdev, timer); 217c4f07eceSBrian Norris timer->dev = dev; 218c4f07eceSBrian Norris 21909ef18bcSYueHaibing timer->base = devm_platform_ioremap_resource(pdev, 0); 220c4f07eceSBrian Norris if (IS_ERR(timer->base)) 221c4f07eceSBrian Norris return PTR_ERR(timer->base); 222c4f07eceSBrian Norris 2232abf286aSAlexandre Belloni timer->rtc = devm_rtc_allocate_device(dev); 2242abf286aSAlexandre Belloni if (IS_ERR(timer->rtc)) 2252abf286aSAlexandre Belloni return PTR_ERR(timer->rtc); 2262abf286aSAlexandre Belloni 227c4f07eceSBrian Norris /* 228c4f07eceSBrian Norris * Set wakeup capability before requesting wakeup interrupt, so we can 229c4f07eceSBrian Norris * process boot-time "wakeups" (e.g., from S5 soft-off) 230c4f07eceSBrian Norris */ 231*2cd98b22SDoug Berger device_init_wakeup(dev, true); 232c4f07eceSBrian Norris 233c4f07eceSBrian Norris timer->irq = platform_get_irq(pdev, 0); 234c4f07eceSBrian Norris if (timer->irq < 0) 235c4f07eceSBrian Norris return -ENODEV; 236c4f07eceSBrian Norris 237c4f07eceSBrian Norris timer->clk = devm_clk_get(dev, NULL); 238c4f07eceSBrian Norris if (!IS_ERR(timer->clk)) { 239c4f07eceSBrian Norris ret = clk_prepare_enable(timer->clk); 240c4f07eceSBrian Norris if (ret) 241c4f07eceSBrian Norris return ret; 242c4f07eceSBrian Norris timer->rate = clk_get_rate(timer->clk); 243c4f07eceSBrian Norris if (!timer->rate) 244c4f07eceSBrian Norris timer->rate = BRCMSTB_WKTMR_DEFAULT_FREQ; 245c4f07eceSBrian Norris } else { 246c4f07eceSBrian Norris timer->rate = BRCMSTB_WKTMR_DEFAULT_FREQ; 247c4f07eceSBrian Norris timer->clk = NULL; 248c4f07eceSBrian Norris } 249c4f07eceSBrian Norris 250c4f07eceSBrian Norris ret = devm_request_irq(dev, timer->irq, brcmstb_waketmr_irq, 0, 251c4f07eceSBrian Norris "brcmstb-waketimer", timer); 252c4f07eceSBrian Norris if (ret < 0) 253f2eef045SAlexey Khoroshilov goto err_clk; 254c4f07eceSBrian Norris 255c4f07eceSBrian Norris timer->reboot_notifier.notifier_call = brcmstb_waketmr_reboot; 256c4f07eceSBrian Norris register_reboot_notifier(&timer->reboot_notifier); 257c4f07eceSBrian Norris 2582abf286aSAlexandre Belloni timer->rtc->ops = &brcmstb_waketmr_ops; 259ded67666SAlexandre Belloni timer->rtc->range_max = U32_MAX; 2602abf286aSAlexandre Belloni 261fdcfd854SBartosz Golaszewski ret = devm_rtc_register_device(timer->rtc); 26244c638ceSAlexandre Belloni if (ret) 263f2eef045SAlexey Khoroshilov goto err_notifier; 264c4f07eceSBrian Norris 265c4f07eceSBrian Norris dev_info(dev, "registered, with irq %d\n", timer->irq); 266c4f07eceSBrian Norris 267f2eef045SAlexey Khoroshilov return 0; 268f2eef045SAlexey Khoroshilov 269f2eef045SAlexey Khoroshilov err_notifier: 270f2eef045SAlexey Khoroshilov unregister_reboot_notifier(&timer->reboot_notifier); 271f2eef045SAlexey Khoroshilov 272f2eef045SAlexey Khoroshilov err_clk: 273f2eef045SAlexey Khoroshilov clk_disable_unprepare(timer->clk); 274f2eef045SAlexey Khoroshilov 275c4f07eceSBrian Norris return ret; 276c4f07eceSBrian Norris } 277c4f07eceSBrian Norris 278c4f07eceSBrian Norris static int brcmstb_waketmr_remove(struct platform_device *pdev) 279c4f07eceSBrian Norris { 280c4f07eceSBrian Norris struct brcmstb_waketmr *timer = dev_get_drvdata(&pdev->dev); 281c4f07eceSBrian Norris 282c4f07eceSBrian Norris unregister_reboot_notifier(&timer->reboot_notifier); 28394303f89SChuhong Yuan clk_disable_unprepare(timer->clk); 284c4f07eceSBrian Norris 285c4f07eceSBrian Norris return 0; 286c4f07eceSBrian Norris } 287c4f07eceSBrian Norris 288c4f07eceSBrian Norris #ifdef CONFIG_PM_SLEEP 289c4f07eceSBrian Norris static int brcmstb_waketmr_suspend(struct device *dev) 290c4f07eceSBrian Norris { 291c4f07eceSBrian Norris struct brcmstb_waketmr *timer = dev_get_drvdata(dev); 292c4f07eceSBrian Norris 293c4f07eceSBrian Norris return brcmstb_waketmr_prepare_suspend(timer); 294c4f07eceSBrian Norris } 295c4f07eceSBrian Norris 296c4f07eceSBrian Norris static int brcmstb_waketmr_resume(struct device *dev) 297c4f07eceSBrian Norris { 298c4f07eceSBrian Norris struct brcmstb_waketmr *timer = dev_get_drvdata(dev); 299c4f07eceSBrian Norris int ret; 300c4f07eceSBrian Norris 301c4f07eceSBrian Norris if (!device_may_wakeup(dev)) 302c4f07eceSBrian Norris return 0; 303c4f07eceSBrian Norris 304c4f07eceSBrian Norris ret = disable_irq_wake(timer->irq); 305c4f07eceSBrian Norris 306c4f07eceSBrian Norris brcmstb_waketmr_clear_alarm(timer); 307c4f07eceSBrian Norris 308c4f07eceSBrian Norris return ret; 309c4f07eceSBrian Norris } 310c4f07eceSBrian Norris #endif /* CONFIG_PM_SLEEP */ 311c4f07eceSBrian Norris 312c4f07eceSBrian Norris static SIMPLE_DEV_PM_OPS(brcmstb_waketmr_pm_ops, 313c4f07eceSBrian Norris brcmstb_waketmr_suspend, brcmstb_waketmr_resume); 314c4f07eceSBrian Norris 315ae1247f7SAlexandre Belloni static const __maybe_unused struct of_device_id brcmstb_waketmr_of_match[] = { 316c4f07eceSBrian Norris { .compatible = "brcm,brcmstb-waketimer" }, 317c4f07eceSBrian Norris { /* sentinel */ }, 318c4f07eceSBrian Norris }; 319c4f07eceSBrian Norris 320c4f07eceSBrian Norris static struct platform_driver brcmstb_waketmr_driver = { 321c4f07eceSBrian Norris .probe = brcmstb_waketmr_probe, 322c4f07eceSBrian Norris .remove = brcmstb_waketmr_remove, 323c4f07eceSBrian Norris .driver = { 324c4f07eceSBrian Norris .name = "brcmstb-waketimer", 325c4f07eceSBrian Norris .pm = &brcmstb_waketmr_pm_ops, 326c4f07eceSBrian Norris .of_match_table = of_match_ptr(brcmstb_waketmr_of_match), 327c4f07eceSBrian Norris } 328c4f07eceSBrian Norris }; 329c4f07eceSBrian Norris module_platform_driver(brcmstb_waketmr_driver); 330c4f07eceSBrian Norris 331c4f07eceSBrian Norris MODULE_LICENSE("GPL v2"); 332c4f07eceSBrian Norris MODULE_AUTHOR("Brian Norris"); 333c4f07eceSBrian Norris MODULE_AUTHOR("Markus Mayer"); 334c4f07eceSBrian Norris MODULE_DESCRIPTION("Wake-up timer driver for STB chips"); 335