]> git.kernelconcepts.de Git - karo-tx-linux.git/commitdiff
rtc: brcmstb-waketimer: Add Broadcom STB wake-timer
authorBrian Norris <computersforpeace@gmail.com>
Mon, 26 Jun 2017 21:15:03 +0000 (14:15 -0700)
committerAlexandre Belloni <alexandre.belloni@free-electrons.com>
Wed, 5 Jul 2017 21:11:59 +0000 (23:11 +0200)
This adds support for the Broadcom STB wake-timer which is a timer in
the chip's 27Mhz clock domain that offers the ability to wake the system
(wake-up source) from suspend states (S2, S3, S5). It is supported using
the rtc framework allowing us to configure alarms for system wake-up.

Signed-off-by: Brian Norris <computersforpeace@gmail.com>
Signed-off-by: Markus Mayer <mmayer@broadcom.com>
Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Alexandre Belloni <alexandre.belloni@free-electrons.com>
drivers/rtc/Kconfig
drivers/rtc/Makefile
drivers/rtc/rtc-brcmstb-waketimer.c [new file with mode: 0644]

index 8d3b9572832666a5bbe5f5be8ea5a5f0b8efa29e..8e1aa67fe533f546774abc2556e630120350c414 100644 (file)
@@ -197,6 +197,17 @@ config RTC_DRV_AC100
          This driver can also be built as a module. If so, the module
          will be called rtc-ac100.
 
+config RTC_DRV_BRCMSTB
+       tristate "Broadcom STB wake-timer"
+       depends on ARCH_BRCMSTB || BMIPS_GENERIC || COMPILE_TEST
+       default ARCH_BRCMSTB || BMIPS_GENERIC
+       help
+         If you say yes here you get support for the wake-timer found on
+         Broadcom STB SoCs (BCM7xxx).
+
+         This driver can also be built as a module. If so, the module will
+         be called rtc-brcmstb-waketimer.
+
 config RTC_DRV_AS3722
        tristate "ams AS3722 RTC driver"
        depends on MFD_AS3722
index 13857d2fce09b24320db136c4e8deedc2f850259..df89cac1f9ae75c3bf799c899d8d6002d005026c 100644 (file)
@@ -36,6 +36,7 @@ obj-$(CONFIG_RTC_DRV_AT91RM9200)+= rtc-at91rm9200.o
 obj-$(CONFIG_RTC_DRV_AT91SAM9) += rtc-at91sam9.o
 obj-$(CONFIG_RTC_DRV_AU1XXX)   += rtc-au1xxx.o
 obj-$(CONFIG_RTC_DRV_BFIN)     += rtc-bfin.o
+obj-$(CONFIG_RTC_DRV_BRCMSTB)  += rtc-brcmstb-waketimer.o
 obj-$(CONFIG_RTC_DRV_BQ32K)    += rtc-bq32k.o
 obj-$(CONFIG_RTC_DRV_BQ4802)   += rtc-bq4802.o
 obj-$(CONFIG_RTC_DRV_CMOS)     += rtc-cmos.o
diff --git a/drivers/rtc/rtc-brcmstb-waketimer.c b/drivers/rtc/rtc-brcmstb-waketimer.c
new file mode 100644 (file)
index 0000000..796ac79
--- /dev/null
@@ -0,0 +1,330 @@
+/*
+ * Copyright © 2014-2017 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt)    KBUILD_MODNAME ": " fmt
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irqreturn.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/pm_wakeup.h>
+#include <linux/reboot.h>
+#include <linux/rtc.h>
+#include <linux/stat.h>
+#include <linux/suspend.h>
+
+struct brcmstb_waketmr {
+       struct rtc_device *rtc;
+       struct device *dev;
+       void __iomem *base;
+       int irq;
+       struct notifier_block reboot_notifier;
+       struct clk *clk;
+       u32 rate;
+};
+
+#define BRCMSTB_WKTMR_EVENT            0x00
+#define BRCMSTB_WKTMR_COUNTER          0x04
+#define BRCMSTB_WKTMR_ALARM            0x08
+#define BRCMSTB_WKTMR_PRESCALER                0x0C
+#define BRCMSTB_WKTMR_PRESCALER_VAL    0x10
+
+#define BRCMSTB_WKTMR_DEFAULT_FREQ     27000000
+
+static inline void brcmstb_waketmr_clear_alarm(struct brcmstb_waketmr *timer)
+{
+       writel_relaxed(1, timer->base + BRCMSTB_WKTMR_EVENT);
+       (void)readl_relaxed(timer->base + BRCMSTB_WKTMR_EVENT);
+}
+
+static void brcmstb_waketmr_set_alarm(struct brcmstb_waketmr *timer,
+                                     unsigned int secs)
+{
+       brcmstb_waketmr_clear_alarm(timer);
+
+       writel_relaxed(secs + 1, timer->base + BRCMSTB_WKTMR_ALARM);
+}
+
+static irqreturn_t brcmstb_waketmr_irq(int irq, void *data)
+{
+       struct brcmstb_waketmr *timer = data;
+
+       pm_wakeup_event(timer->dev, 0);
+
+       return IRQ_HANDLED;
+}
+
+struct wktmr_time {
+       u32 sec;
+       u32 pre;
+};
+
+static void wktmr_read(struct brcmstb_waketmr *timer,
+                      struct wktmr_time *t)
+{
+       u32 tmp;
+
+       do {
+               t->sec = readl_relaxed(timer->base + BRCMSTB_WKTMR_COUNTER);
+               tmp = readl_relaxed(timer->base + BRCMSTB_WKTMR_PRESCALER_VAL);
+       } while (tmp >= timer->rate);
+
+       t->pre = timer->rate - tmp;
+}
+
+static int brcmstb_waketmr_prepare_suspend(struct brcmstb_waketmr *timer)
+{
+       struct device *dev = timer->dev;
+       int ret = 0;
+
+       if (device_may_wakeup(dev)) {
+               ret = enable_irq_wake(timer->irq);
+               if (ret) {
+                       dev_err(dev, "failed to enable wake-up interrupt\n");
+                       return ret;
+               }
+       }
+
+       return ret;
+}
+
+/* If enabled as a wakeup-source, arm the timer when powering off */
+static int brcmstb_waketmr_reboot(struct notifier_block *nb,
+               unsigned long action, void *data)
+{
+       struct brcmstb_waketmr *timer;
+
+       timer = container_of(nb, struct brcmstb_waketmr, reboot_notifier);
+
+       /* Set timer for cold boot */
+       if (action == SYS_POWER_OFF)
+               brcmstb_waketmr_prepare_suspend(timer);
+
+       return NOTIFY_DONE;
+}
+
+static int brcmstb_waketmr_gettime(struct device *dev,
+                                  struct rtc_time *tm)
+{
+       struct brcmstb_waketmr *timer = dev_get_drvdata(dev);
+       struct wktmr_time now;
+
+       wktmr_read(timer, &now);
+
+       rtc_time_to_tm(now.sec, tm);
+
+       return 0;
+}
+
+static int brcmstb_waketmr_settime(struct device *dev,
+                                  struct rtc_time *tm)
+{
+       struct brcmstb_waketmr *timer = dev_get_drvdata(dev);
+       time64_t sec;
+
+       sec = rtc_tm_to_time64(tm);
+
+       if (sec > U32_MAX || sec < 0)
+               return -EINVAL;
+
+       writel_relaxed(sec, timer->base + BRCMSTB_WKTMR_COUNTER);
+
+       return 0;
+}
+
+static int brcmstb_waketmr_getalarm(struct device *dev,
+                                   struct rtc_wkalrm *alarm)
+{
+       struct brcmstb_waketmr *timer = dev_get_drvdata(dev);
+       time64_t sec;
+       u32 reg;
+
+       sec = readl_relaxed(timer->base + BRCMSTB_WKTMR_ALARM);
+       if (sec != 0) {
+               /* Alarm is enabled */
+               alarm->enabled = 1;
+               rtc_time64_to_tm(sec, &alarm->time);
+       }
+
+       reg = readl_relaxed(timer->base + BRCMSTB_WKTMR_EVENT);
+       alarm->pending = !!(reg & 1);
+
+       return 0;
+}
+
+static int brcmstb_waketmr_setalarm(struct device *dev,
+                                    struct rtc_wkalrm *alarm)
+{
+       struct brcmstb_waketmr *timer = dev_get_drvdata(dev);
+       time64_t sec;
+
+       if (alarm->enabled)
+               sec = rtc_tm_to_time64(&alarm->time);
+       else
+               sec = 0;
+
+       if (sec > U32_MAX || sec < 0)
+               return -EINVAL;
+
+       brcmstb_waketmr_set_alarm(timer, sec);
+
+       return 0;
+}
+
+/*
+ * Does not do much but keep the RTC class happy. We always support
+ * alarms.
+ */
+static int brcmstb_waketmr_alarm_enable(struct device *dev,
+                                       unsigned int enabled)
+{
+       return 0;
+}
+
+static const struct rtc_class_ops brcmstb_waketmr_ops = {
+       .read_time      = brcmstb_waketmr_gettime,
+       .set_time       = brcmstb_waketmr_settime,
+       .read_alarm     = brcmstb_waketmr_getalarm,
+       .set_alarm      = brcmstb_waketmr_setalarm,
+       .alarm_irq_enable = brcmstb_waketmr_alarm_enable,
+};
+
+static int brcmstb_waketmr_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct brcmstb_waketmr *timer;
+       struct resource *res;
+       int ret;
+
+       timer = devm_kzalloc(dev, sizeof(*timer), GFP_KERNEL);
+       if (!timer)
+               return -ENOMEM;
+
+       platform_set_drvdata(pdev, timer);
+       timer->dev = dev;
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       timer->base = devm_ioremap_resource(dev, res);
+       if (IS_ERR(timer->base))
+               return PTR_ERR(timer->base);
+
+       /*
+        * Set wakeup capability before requesting wakeup interrupt, so we can
+        * process boot-time "wakeups" (e.g., from S5 soft-off)
+        */
+       device_set_wakeup_capable(dev, true);
+       device_wakeup_enable(dev);
+
+       timer->irq = platform_get_irq(pdev, 0);
+       if (timer->irq < 0)
+               return -ENODEV;
+
+       timer->clk = devm_clk_get(dev, NULL);
+       if (!IS_ERR(timer->clk)) {
+               ret = clk_prepare_enable(timer->clk);
+               if (ret)
+                       return ret;
+               timer->rate = clk_get_rate(timer->clk);
+               if (!timer->rate)
+                       timer->rate = BRCMSTB_WKTMR_DEFAULT_FREQ;
+       } else {
+               timer->rate = BRCMSTB_WKTMR_DEFAULT_FREQ;
+               timer->clk = NULL;
+       }
+
+       ret = devm_request_irq(dev, timer->irq, brcmstb_waketmr_irq, 0,
+                              "brcmstb-waketimer", timer);
+       if (ret < 0)
+               return ret;
+
+       timer->reboot_notifier.notifier_call = brcmstb_waketmr_reboot;
+       register_reboot_notifier(&timer->reboot_notifier);
+
+       timer->rtc = rtc_device_register("brcmstb-waketmr", dev,
+                                        &brcmstb_waketmr_ops, THIS_MODULE);
+       if (IS_ERR(timer->rtc)) {
+               dev_err(dev, "unable to register device\n");
+               unregister_reboot_notifier(&timer->reboot_notifier);
+               return PTR_ERR(timer->rtc);
+       }
+
+       dev_info(dev, "registered, with irq %d\n", timer->irq);
+
+       return ret;
+}
+
+static int brcmstb_waketmr_remove(struct platform_device *pdev)
+{
+       struct brcmstb_waketmr *timer = dev_get_drvdata(&pdev->dev);
+
+       unregister_reboot_notifier(&timer->reboot_notifier);
+       rtc_device_unregister(timer->rtc);
+
+       return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int brcmstb_waketmr_suspend(struct device *dev)
+{
+       struct brcmstb_waketmr *timer = dev_get_drvdata(dev);
+
+       return brcmstb_waketmr_prepare_suspend(timer);
+}
+
+static int brcmstb_waketmr_resume(struct device *dev)
+{
+       struct brcmstb_waketmr *timer = dev_get_drvdata(dev);
+       int ret;
+
+       if (!device_may_wakeup(dev))
+               return 0;
+
+       ret = disable_irq_wake(timer->irq);
+
+       brcmstb_waketmr_clear_alarm(timer);
+
+       return ret;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static SIMPLE_DEV_PM_OPS(brcmstb_waketmr_pm_ops,
+                        brcmstb_waketmr_suspend, brcmstb_waketmr_resume);
+
+static const struct of_device_id brcmstb_waketmr_of_match[] = {
+       { .compatible = "brcm,brcmstb-waketimer" },
+       { /* sentinel */ },
+};
+
+static struct platform_driver brcmstb_waketmr_driver = {
+       .probe                  = brcmstb_waketmr_probe,
+       .remove                 = brcmstb_waketmr_remove,
+       .driver = {
+               .name           = "brcmstb-waketimer",
+               .pm             = &brcmstb_waketmr_pm_ops,
+               .of_match_table = of_match_ptr(brcmstb_waketmr_of_match),
+       }
+};
+module_platform_driver(brcmstb_waketmr_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Brian Norris");
+MODULE_AUTHOR("Markus Mayer");
+MODULE_DESCRIPTION("Wake-up timer driver for STB chips");