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 37c4f07eceSBrian Norris #define BRCMSTB_WKTMR_COUNTER 0x04 38c4f07eceSBrian Norris #define BRCMSTB_WKTMR_ALARM 0x08 39c4f07eceSBrian Norris #define BRCMSTB_WKTMR_PRESCALER 0x0C 40c4f07eceSBrian Norris #define BRCMSTB_WKTMR_PRESCALER_VAL 0x10 41c4f07eceSBrian Norris 42c4f07eceSBrian Norris #define BRCMSTB_WKTMR_DEFAULT_FREQ 27000000 43c4f07eceSBrian Norris 44c4f07eceSBrian Norris static inline void brcmstb_waketmr_clear_alarm(struct brcmstb_waketmr *timer) 45c4f07eceSBrian Norris { 46c4f07eceSBrian Norris writel_relaxed(1, timer->base + BRCMSTB_WKTMR_EVENT); 47c4f07eceSBrian Norris (void)readl_relaxed(timer->base + BRCMSTB_WKTMR_EVENT); 48c4f07eceSBrian Norris } 49c4f07eceSBrian Norris 50c4f07eceSBrian Norris static void brcmstb_waketmr_set_alarm(struct brcmstb_waketmr *timer, 51c4f07eceSBrian Norris unsigned int secs) 52c4f07eceSBrian Norris { 53c4f07eceSBrian Norris brcmstb_waketmr_clear_alarm(timer); 54c4f07eceSBrian Norris 5558d3d5e7SJustin Chen /* Make sure we are actually counting in seconds */ 5658d3d5e7SJustin Chen writel_relaxed(timer->rate, timer->base + BRCMSTB_WKTMR_PRESCALER); 5758d3d5e7SJustin Chen 58c4f07eceSBrian Norris writel_relaxed(secs + 1, timer->base + BRCMSTB_WKTMR_ALARM); 59c4f07eceSBrian Norris } 60c4f07eceSBrian Norris 61c4f07eceSBrian Norris static irqreturn_t brcmstb_waketmr_irq(int irq, void *data) 62c4f07eceSBrian Norris { 63c4f07eceSBrian Norris struct brcmstb_waketmr *timer = data; 64c4f07eceSBrian Norris 65c4f07eceSBrian Norris pm_wakeup_event(timer->dev, 0); 66c4f07eceSBrian Norris 67c4f07eceSBrian Norris return IRQ_HANDLED; 68c4f07eceSBrian Norris } 69c4f07eceSBrian Norris 70c4f07eceSBrian Norris struct wktmr_time { 71c4f07eceSBrian Norris u32 sec; 72c4f07eceSBrian Norris u32 pre; 73c4f07eceSBrian Norris }; 74c4f07eceSBrian Norris 75c4f07eceSBrian Norris static void wktmr_read(struct brcmstb_waketmr *timer, 76c4f07eceSBrian Norris struct wktmr_time *t) 77c4f07eceSBrian Norris { 78c4f07eceSBrian Norris u32 tmp; 79c4f07eceSBrian Norris 80c4f07eceSBrian Norris do { 81c4f07eceSBrian Norris t->sec = readl_relaxed(timer->base + BRCMSTB_WKTMR_COUNTER); 82c4f07eceSBrian Norris tmp = readl_relaxed(timer->base + BRCMSTB_WKTMR_PRESCALER_VAL); 83c4f07eceSBrian Norris } while (tmp >= timer->rate); 84c4f07eceSBrian Norris 85c4f07eceSBrian Norris t->pre = timer->rate - tmp; 86c4f07eceSBrian Norris } 87c4f07eceSBrian Norris 88c4f07eceSBrian Norris static int brcmstb_waketmr_prepare_suspend(struct brcmstb_waketmr *timer) 89c4f07eceSBrian Norris { 90c4f07eceSBrian Norris struct device *dev = timer->dev; 91c4f07eceSBrian Norris int ret = 0; 92c4f07eceSBrian Norris 93c4f07eceSBrian Norris if (device_may_wakeup(dev)) { 94c4f07eceSBrian Norris ret = enable_irq_wake(timer->irq); 95c4f07eceSBrian Norris if (ret) { 96c4f07eceSBrian Norris dev_err(dev, "failed to enable wake-up interrupt\n"); 97c4f07eceSBrian Norris return ret; 98c4f07eceSBrian Norris } 99c4f07eceSBrian Norris } 100c4f07eceSBrian Norris 101c4f07eceSBrian Norris return ret; 102c4f07eceSBrian Norris } 103c4f07eceSBrian Norris 104c4f07eceSBrian Norris /* If enabled as a wakeup-source, arm the timer when powering off */ 105c4f07eceSBrian Norris static int brcmstb_waketmr_reboot(struct notifier_block *nb, 106c4f07eceSBrian Norris unsigned long action, void *data) 107c4f07eceSBrian Norris { 108c4f07eceSBrian Norris struct brcmstb_waketmr *timer; 109c4f07eceSBrian Norris 110c4f07eceSBrian Norris timer = container_of(nb, struct brcmstb_waketmr, reboot_notifier); 111c4f07eceSBrian Norris 112c4f07eceSBrian Norris /* Set timer for cold boot */ 113c4f07eceSBrian Norris if (action == SYS_POWER_OFF) 114c4f07eceSBrian Norris brcmstb_waketmr_prepare_suspend(timer); 115c4f07eceSBrian Norris 116c4f07eceSBrian Norris return NOTIFY_DONE; 117c4f07eceSBrian Norris } 118c4f07eceSBrian Norris 119c4f07eceSBrian Norris static int brcmstb_waketmr_gettime(struct device *dev, 120c4f07eceSBrian Norris struct rtc_time *tm) 121c4f07eceSBrian Norris { 122c4f07eceSBrian Norris struct brcmstb_waketmr *timer = dev_get_drvdata(dev); 123c4f07eceSBrian Norris struct wktmr_time now; 124c4f07eceSBrian Norris 125c4f07eceSBrian Norris wktmr_read(timer, &now); 126c4f07eceSBrian Norris 1279a8f2d12SAlexandre Belloni rtc_time64_to_tm(now.sec, tm); 128c4f07eceSBrian Norris 129c4f07eceSBrian Norris return 0; 130c4f07eceSBrian Norris } 131c4f07eceSBrian Norris 132c4f07eceSBrian Norris static int brcmstb_waketmr_settime(struct device *dev, 133c4f07eceSBrian Norris struct rtc_time *tm) 134c4f07eceSBrian Norris { 135c4f07eceSBrian Norris struct brcmstb_waketmr *timer = dev_get_drvdata(dev); 136c4f07eceSBrian Norris time64_t sec; 137c4f07eceSBrian Norris 138c4f07eceSBrian Norris sec = rtc_tm_to_time64(tm); 139c4f07eceSBrian Norris 140c4f07eceSBrian Norris writel_relaxed(sec, timer->base + BRCMSTB_WKTMR_COUNTER); 141c4f07eceSBrian Norris 142c4f07eceSBrian Norris return 0; 143c4f07eceSBrian Norris } 144c4f07eceSBrian Norris 145c4f07eceSBrian Norris static int brcmstb_waketmr_getalarm(struct device *dev, 146c4f07eceSBrian Norris struct rtc_wkalrm *alarm) 147c4f07eceSBrian Norris { 148c4f07eceSBrian Norris struct brcmstb_waketmr *timer = dev_get_drvdata(dev); 149c4f07eceSBrian Norris time64_t sec; 150c4f07eceSBrian Norris u32 reg; 151c4f07eceSBrian Norris 152c4f07eceSBrian Norris sec = readl_relaxed(timer->base + BRCMSTB_WKTMR_ALARM); 153c4f07eceSBrian Norris if (sec != 0) { 154c4f07eceSBrian Norris /* Alarm is enabled */ 155c4f07eceSBrian Norris alarm->enabled = 1; 156c4f07eceSBrian Norris rtc_time64_to_tm(sec, &alarm->time); 157c4f07eceSBrian Norris } 158c4f07eceSBrian Norris 159c4f07eceSBrian Norris reg = readl_relaxed(timer->base + BRCMSTB_WKTMR_EVENT); 160c4f07eceSBrian Norris alarm->pending = !!(reg & 1); 161c4f07eceSBrian Norris 162c4f07eceSBrian Norris return 0; 163c4f07eceSBrian Norris } 164c4f07eceSBrian Norris 165c4f07eceSBrian Norris static int brcmstb_waketmr_setalarm(struct device *dev, 166c4f07eceSBrian Norris struct rtc_wkalrm *alarm) 167c4f07eceSBrian Norris { 168c4f07eceSBrian Norris struct brcmstb_waketmr *timer = dev_get_drvdata(dev); 169c4f07eceSBrian Norris time64_t sec; 170c4f07eceSBrian Norris 171c4f07eceSBrian Norris if (alarm->enabled) 172c4f07eceSBrian Norris sec = rtc_tm_to_time64(&alarm->time); 173c4f07eceSBrian Norris else 174c4f07eceSBrian Norris sec = 0; 175c4f07eceSBrian Norris 176c4f07eceSBrian Norris brcmstb_waketmr_set_alarm(timer, sec); 177c4f07eceSBrian Norris 178c4f07eceSBrian Norris return 0; 179c4f07eceSBrian Norris } 180c4f07eceSBrian Norris 181c4f07eceSBrian Norris /* 182c4f07eceSBrian Norris * Does not do much but keep the RTC class happy. We always support 183c4f07eceSBrian Norris * alarms. 184c4f07eceSBrian Norris */ 185c4f07eceSBrian Norris static int brcmstb_waketmr_alarm_enable(struct device *dev, 186c4f07eceSBrian Norris unsigned int enabled) 187c4f07eceSBrian Norris { 188c4f07eceSBrian Norris return 0; 189c4f07eceSBrian Norris } 190c4f07eceSBrian Norris 191c4f07eceSBrian Norris static const struct rtc_class_ops brcmstb_waketmr_ops = { 192c4f07eceSBrian Norris .read_time = brcmstb_waketmr_gettime, 193c4f07eceSBrian Norris .set_time = brcmstb_waketmr_settime, 194c4f07eceSBrian Norris .read_alarm = brcmstb_waketmr_getalarm, 195c4f07eceSBrian Norris .set_alarm = brcmstb_waketmr_setalarm, 196c4f07eceSBrian Norris .alarm_irq_enable = brcmstb_waketmr_alarm_enable, 197c4f07eceSBrian Norris }; 198c4f07eceSBrian Norris 199c4f07eceSBrian Norris static int brcmstb_waketmr_probe(struct platform_device *pdev) 200c4f07eceSBrian Norris { 201c4f07eceSBrian Norris struct device *dev = &pdev->dev; 202c4f07eceSBrian Norris struct brcmstb_waketmr *timer; 203c4f07eceSBrian Norris int ret; 204c4f07eceSBrian Norris 205c4f07eceSBrian Norris timer = devm_kzalloc(dev, sizeof(*timer), GFP_KERNEL); 206c4f07eceSBrian Norris if (!timer) 207c4f07eceSBrian Norris return -ENOMEM; 208c4f07eceSBrian Norris 209c4f07eceSBrian Norris platform_set_drvdata(pdev, timer); 210c4f07eceSBrian Norris timer->dev = dev; 211c4f07eceSBrian Norris 21209ef18bcSYueHaibing timer->base = devm_platform_ioremap_resource(pdev, 0); 213c4f07eceSBrian Norris if (IS_ERR(timer->base)) 214c4f07eceSBrian Norris return PTR_ERR(timer->base); 215c4f07eceSBrian Norris 2162abf286aSAlexandre Belloni timer->rtc = devm_rtc_allocate_device(dev); 2172abf286aSAlexandre Belloni if (IS_ERR(timer->rtc)) 2182abf286aSAlexandre Belloni return PTR_ERR(timer->rtc); 2192abf286aSAlexandre Belloni 220c4f07eceSBrian Norris /* 221c4f07eceSBrian Norris * Set wakeup capability before requesting wakeup interrupt, so we can 222c4f07eceSBrian Norris * process boot-time "wakeups" (e.g., from S5 soft-off) 223c4f07eceSBrian Norris */ 224c4f07eceSBrian Norris device_set_wakeup_capable(dev, true); 225c4f07eceSBrian Norris device_wakeup_enable(dev); 226c4f07eceSBrian Norris 227c4f07eceSBrian Norris timer->irq = platform_get_irq(pdev, 0); 228c4f07eceSBrian Norris if (timer->irq < 0) 229c4f07eceSBrian Norris return -ENODEV; 230c4f07eceSBrian Norris 231c4f07eceSBrian Norris timer->clk = devm_clk_get(dev, NULL); 232c4f07eceSBrian Norris if (!IS_ERR(timer->clk)) { 233c4f07eceSBrian Norris ret = clk_prepare_enable(timer->clk); 234c4f07eceSBrian Norris if (ret) 235c4f07eceSBrian Norris return ret; 236c4f07eceSBrian Norris timer->rate = clk_get_rate(timer->clk); 237c4f07eceSBrian Norris if (!timer->rate) 238c4f07eceSBrian Norris timer->rate = BRCMSTB_WKTMR_DEFAULT_FREQ; 239c4f07eceSBrian Norris } else { 240c4f07eceSBrian Norris timer->rate = BRCMSTB_WKTMR_DEFAULT_FREQ; 241c4f07eceSBrian Norris timer->clk = NULL; 242c4f07eceSBrian Norris } 243c4f07eceSBrian Norris 244c4f07eceSBrian Norris ret = devm_request_irq(dev, timer->irq, brcmstb_waketmr_irq, 0, 245c4f07eceSBrian Norris "brcmstb-waketimer", timer); 246c4f07eceSBrian Norris if (ret < 0) 247f2eef045SAlexey Khoroshilov goto err_clk; 248c4f07eceSBrian Norris 249c4f07eceSBrian Norris timer->reboot_notifier.notifier_call = brcmstb_waketmr_reboot; 250c4f07eceSBrian Norris register_reboot_notifier(&timer->reboot_notifier); 251c4f07eceSBrian Norris 2522abf286aSAlexandre Belloni timer->rtc->ops = &brcmstb_waketmr_ops; 253ded67666SAlexandre Belloni timer->rtc->range_max = U32_MAX; 2542abf286aSAlexandre Belloni 255*fdcfd854SBartosz Golaszewski ret = devm_rtc_register_device(timer->rtc); 25644c638ceSAlexandre Belloni if (ret) 257f2eef045SAlexey Khoroshilov goto err_notifier; 258c4f07eceSBrian Norris 259c4f07eceSBrian Norris dev_info(dev, "registered, with irq %d\n", timer->irq); 260c4f07eceSBrian Norris 261f2eef045SAlexey Khoroshilov return 0; 262f2eef045SAlexey Khoroshilov 263f2eef045SAlexey Khoroshilov err_notifier: 264f2eef045SAlexey Khoroshilov unregister_reboot_notifier(&timer->reboot_notifier); 265f2eef045SAlexey Khoroshilov 266f2eef045SAlexey Khoroshilov err_clk: 267f2eef045SAlexey Khoroshilov clk_disable_unprepare(timer->clk); 268f2eef045SAlexey Khoroshilov 269c4f07eceSBrian Norris return ret; 270c4f07eceSBrian Norris } 271c4f07eceSBrian Norris 272c4f07eceSBrian Norris static int brcmstb_waketmr_remove(struct platform_device *pdev) 273c4f07eceSBrian Norris { 274c4f07eceSBrian Norris struct brcmstb_waketmr *timer = dev_get_drvdata(&pdev->dev); 275c4f07eceSBrian Norris 276c4f07eceSBrian Norris unregister_reboot_notifier(&timer->reboot_notifier); 27794303f89SChuhong Yuan clk_disable_unprepare(timer->clk); 278c4f07eceSBrian Norris 279c4f07eceSBrian Norris return 0; 280c4f07eceSBrian Norris } 281c4f07eceSBrian Norris 282c4f07eceSBrian Norris #ifdef CONFIG_PM_SLEEP 283c4f07eceSBrian Norris static int brcmstb_waketmr_suspend(struct device *dev) 284c4f07eceSBrian Norris { 285c4f07eceSBrian Norris struct brcmstb_waketmr *timer = dev_get_drvdata(dev); 286c4f07eceSBrian Norris 287c4f07eceSBrian Norris return brcmstb_waketmr_prepare_suspend(timer); 288c4f07eceSBrian Norris } 289c4f07eceSBrian Norris 290c4f07eceSBrian Norris static int brcmstb_waketmr_resume(struct device *dev) 291c4f07eceSBrian Norris { 292c4f07eceSBrian Norris struct brcmstb_waketmr *timer = dev_get_drvdata(dev); 293c4f07eceSBrian Norris int ret; 294c4f07eceSBrian Norris 295c4f07eceSBrian Norris if (!device_may_wakeup(dev)) 296c4f07eceSBrian Norris return 0; 297c4f07eceSBrian Norris 298c4f07eceSBrian Norris ret = disable_irq_wake(timer->irq); 299c4f07eceSBrian Norris 300c4f07eceSBrian Norris brcmstb_waketmr_clear_alarm(timer); 301c4f07eceSBrian Norris 302c4f07eceSBrian Norris return ret; 303c4f07eceSBrian Norris } 304c4f07eceSBrian Norris #endif /* CONFIG_PM_SLEEP */ 305c4f07eceSBrian Norris 306c4f07eceSBrian Norris static SIMPLE_DEV_PM_OPS(brcmstb_waketmr_pm_ops, 307c4f07eceSBrian Norris brcmstb_waketmr_suspend, brcmstb_waketmr_resume); 308c4f07eceSBrian Norris 309c4f07eceSBrian Norris static const struct of_device_id brcmstb_waketmr_of_match[] = { 310c4f07eceSBrian Norris { .compatible = "brcm,brcmstb-waketimer" }, 311c4f07eceSBrian Norris { /* sentinel */ }, 312c4f07eceSBrian Norris }; 313c4f07eceSBrian Norris 314c4f07eceSBrian Norris static struct platform_driver brcmstb_waketmr_driver = { 315c4f07eceSBrian Norris .probe = brcmstb_waketmr_probe, 316c4f07eceSBrian Norris .remove = brcmstb_waketmr_remove, 317c4f07eceSBrian Norris .driver = { 318c4f07eceSBrian Norris .name = "brcmstb-waketimer", 319c4f07eceSBrian Norris .pm = &brcmstb_waketmr_pm_ops, 320c4f07eceSBrian Norris .of_match_table = of_match_ptr(brcmstb_waketmr_of_match), 321c4f07eceSBrian Norris } 322c4f07eceSBrian Norris }; 323c4f07eceSBrian Norris module_platform_driver(brcmstb_waketmr_driver); 324c4f07eceSBrian Norris 325c4f07eceSBrian Norris MODULE_LICENSE("GPL v2"); 326c4f07eceSBrian Norris MODULE_AUTHOR("Brian Norris"); 327c4f07eceSBrian Norris MODULE_AUTHOR("Markus Mayer"); 328c4f07eceSBrian Norris MODULE_DESCRIPTION("Wake-up timer driver for STB chips"); 329