/* * Mediatek Watchdog Driver * * Copyright (C) 2014 Matthias Brugger * * Matthias Brugger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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. * * Based on sunxi_wdt.c */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_FIQ_GLUE #include #include #endif #include #include #include #include #include #include #include #include #include #ifdef CONFIG_MT6397_MISC #include #endif #ifdef CONFIG_MTK_RTC #include "../misc/mediatek/include/mt-plat/mtk_rtc.h" #endif #include #define WDT_MAX_TIMEOUT 31 #define WDT_MIN_TIMEOUT 1 #define WDT_LENGTH_TIMEOUT(n) ((n) << 5) #define WDT_LENGTH 0x04 #define WDT_LENGTH_KEY 0x8 #define WDT_RST 0x08 #define WDT_RST_RELOAD 0x1971 #define WDT_MODE 0x00 #define WDT_MODE_EN (1U << 0) #define WDT_MODE_EXT_POL_LOW (0U << 1) #define WDT_MODE_EXT_POL_HIGH (1U << 1) #define WDT_MODE_EXRST_EN (1U << 2) #define WDT_MODE_IRQ_EN (1U << 3) #define WDT_MODE_AUTO_START (1U << 4) #define WDT_MODE_IRQ_LVL (1U << 5) #define WDT_MODE_DUAL_EN (1U << 6) #define WDT_MODE_KEY 0x22000000U #define WDT_STATUS 0x0c #define WDT_NONRST_REG 0x20 #define WDT_NONRST_REG2 0x24 #define WDT_SWRST 0x14 #define WDT_SWRST_KEY 0x1209 #define WDT_SWSYSRST 0x18U #define WDT_SWSYSRST_KEY 0x88000000U #define WDT_REQ_MODE 0x30U #define WDT_REQ_MODE_KEY 0x33000000U #define WDT_REQ_IRQ_EN 0x34U #define WDT_REQ_IRQ_KEY 0x44000000U #define WDT_REQ_MODE_DEBUG_EN 0x80000U #define WDT_LATCH_CTL 0x44 #define WDT_DEBUG_CTL_DVFSRC_EN (1 << 9) #define WDT_LATCH_CTL_DVFSRC (1 << 13) #define WDT_LATCH_CTL_KEY (0x95000000) #define WDT_DEBUG_CTL2 0xA0 #define WDT_DEBUG_CTL2_KEY (0x55000000) #define DRV_NAME "mtk-wdt" #define DRV_VERSION "2.0" static bool nowayout = WATCHDOG_NOWAYOUT; static unsigned int timeout = WDT_MAX_TIMEOUT; #ifdef CONFIG_LOCKUP_DETECTOR extern int softlockup_cpu; #endif struct toprgu_reset { spinlock_t lock; void __iomem *toprgu_swrst_base; int regofs; struct reset_controller_dev rcdev; }; struct mtk_wdt_dev { struct watchdog_device wdt_dev; void __iomem *wdt_base; unsigned int wdt_irq_id; struct notifier_block restart_handler, pm_handler; struct toprgu_reset reset_controller; }; static void __iomem *toprgu_base; static struct watchdog_device *wdt_dev; static int toprgu_reset_assert(struct reset_controller_dev *rcdev, unsigned long id) { unsigned int tmp; unsigned long flags; struct toprgu_reset *data = container_of(rcdev, struct toprgu_reset, rcdev); spin_lock_irqsave(&data->lock, flags); tmp = __raw_readl(data->toprgu_swrst_base + data->regofs); tmp |= BIT(id); tmp |= WDT_SWSYSRST_KEY; writel(tmp, data->toprgu_swrst_base + data->regofs); spin_unlock_irqrestore(&data->lock, flags); return 0; } static int toprgu_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id) { unsigned int tmp; unsigned long flags; struct toprgu_reset *data = container_of(rcdev, struct toprgu_reset, rcdev); spin_lock_irqsave(&data->lock, flags); tmp = __raw_readl(data->toprgu_swrst_base + data->regofs); tmp &= ~BIT(id); tmp |= WDT_SWSYSRST_KEY; writel(tmp, data->toprgu_swrst_base + data->regofs); spin_unlock_irqrestore(&data->lock, flags); return 0; } static int toprgu_reset(struct reset_controller_dev *rcdev, unsigned long id) { int ret; ret = toprgu_reset_assert(rcdev, id); if (ret != 0) return ret; return toprgu_reset_deassert(rcdev, id); } static struct reset_control_ops toprgu_reset_ops = { .assert = toprgu_reset_assert, .deassert = toprgu_reset_deassert, .reset = toprgu_reset, }; static void toprgu_register_reset_controller (struct platform_device *pdev, int regofs) { int ret; struct mtk_wdt_dev *mtk_wdt = platform_get_drvdata(pdev); spin_lock_init(&mtk_wdt->reset_controller.lock); mtk_wdt->reset_controller.toprgu_swrst_base = mtk_wdt->wdt_base; mtk_wdt->reset_controller.regofs = regofs; mtk_wdt->reset_controller.rcdev.owner = THIS_MODULE; mtk_wdt->reset_controller.rcdev.nr_resets = 15; mtk_wdt->reset_controller.rcdev.ops = &toprgu_reset_ops; mtk_wdt->reset_controller.rcdev.of_node = pdev->dev.of_node; ret = reset_controller_register(&mtk_wdt->reset_controller.rcdev); if (ret != 0) pr_err("could not register toprgu reset controller: %d\n", ret); } static int mtk_reset_handler(struct notifier_block *this, unsigned long mode, void *cmd) { struct mtk_wdt_dev *mtk_wdt; void __iomem *wdt_base; u32 reg; mtk_wdt = container_of(this, struct mtk_wdt_dev, restart_handler); wdt_base = mtk_wdt->wdt_base; /* * WDT_STATUS will be cleared to zero after writing to WDT_MODE, * so we backup it in WDT_NONRST_REG, * and then print it out in mtk_wdt_probe() after reset */ writel(__raw_readl(wdt_base + WDT_STATUS), wdt_base + WDT_NONRST_REG); reg = ioread32(wdt_base + WDT_MODE); reg &= ~(WDT_MODE_DUAL_EN | WDT_MODE_IRQ_EN | WDT_MODE_EN | WDT_MODE_AUTO_START); reg |= WDT_MODE_KEY; iowrite32(reg, wdt_base + WDT_MODE); if (cmd && (strcmp(cmd, "rpmbp") == 0)) { iowrite32(ioread32(wdt_base + WDT_NONRST_REG2) | (1U << 3), wdt_base + WDT_NONRST_REG2); /* *Set by pass power key flag *After rpmbp flow, system need enter normal boot flow */ writel(__raw_readl(wdt_base + WDT_MODE) | WDT_MODE_AUTO_START | WDT_MODE_KEY, wdt_base + WDT_MODE); } else if (cmd && (strcmp(cmd, "recovery") == 0)) { iowrite32(ioread32(wdt_base + WDT_NONRST_REG2) | (1U << 1), wdt_base + WDT_NONRST_REG2); #ifdef CONFIG_MT6397_MISC mtk_misc_mark_recovery(); #endif #ifdef CONFIG_MTK_RTC rtc_mark_recovery(); #endif } else if (cmd && (strcmp(cmd, "bootloader") == 0)) { iowrite32(ioread32(wdt_base + WDT_NONRST_REG2) | (1U << 2), wdt_base + WDT_NONRST_REG2); #ifdef CONFIG_MT6397_MISC mtk_misc_mark_fast(); #endif #ifdef CONFIG_MTK_RTC rtc_mark_fast(); #endif } else if (cmd && (strcmp(cmd, "kpoc") == 0)) { #if defined(CONFIG_MTK_RTC) && defined(CONFIG_MTK_KERNEL_POWER_OFF_CHARGING) rtc_mark_kpoc(); #endif } else if (cmd && (strcmp(cmd, "enter_kpoc") == 0)) { #if defined(CONFIG_MTK_RTC) && defined(CONFIG_MTK_AUTO_POWER_ON_WITH_CHARGER) rtc_mark_enter_kpoc(); #endif } else { /* *cmd != NULL means by pass power key reboot, *We using auto_restart bit as by pass power key flag */ writel(__raw_readl(wdt_base + WDT_MODE) | WDT_MODE_AUTO_START | WDT_MODE_KEY, wdt_base + WDT_MODE); } if (!arm_pm_restart) { while (1) { writel(WDT_SWRST_KEY, wdt_base + WDT_SWRST); mdelay(5); } } return NOTIFY_DONE; } static int mtk_wdt_ping(struct watchdog_device *wdt_dev) { struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdt_dev); void __iomem *wdt_base = mtk_wdt->wdt_base; #ifdef CONFIG_LOCKUP_DETECTOR if (softlockup_cpu >= 0) { pr_err("Soft lockup occurred at CPU %d\n", softlockup_cpu); return -1; } #endif iowrite32(WDT_RST_RELOAD, wdt_base + WDT_RST); pr_info("[WDK]: kick Ex WDT\n"); return 0; } static int mtk_pm_handler(struct notifier_block *this, unsigned long pm_event, void *unused) { struct mtk_wdt_dev *mtk_wdt; mtk_wdt = container_of(this, struct mtk_wdt_dev, pm_handler); switch (pm_event) { case PM_SUSPEND_PREPARE: mtk_wdt_ping(&mtk_wdt->wdt_dev); break; default: break; } return NOTIFY_DONE; } static int mtk_wdt_set_timeout(struct watchdog_device *wdt_dev, unsigned int timeout) { struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdt_dev); void __iomem *wdt_base = mtk_wdt->wdt_base; u32 reg; wdt_dev->timeout = timeout; /* * One bit is the value of 512 ticks * The clock has 32 KHz */ reg = WDT_LENGTH_TIMEOUT(timeout << 6) | WDT_LENGTH_KEY; iowrite32(reg, wdt_base + WDT_LENGTH); mtk_wdt_ping(wdt_dev); return 0; } static int mtk_wdt_stop(struct watchdog_device *wdt_dev) { struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdt_dev); void __iomem *wdt_base = mtk_wdt->wdt_base; u32 reg; reg = readl(wdt_base + WDT_MODE); reg &= ~WDT_MODE_EN; reg |= WDT_MODE_KEY; iowrite32(reg, wdt_base + WDT_MODE); return 0; } static int mtk_wdt_start(struct watchdog_device *wdt_dev) { u32 reg; struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdt_dev); void __iomem *wdt_base = mtk_wdt->wdt_base; int ret; ret = mtk_wdt_set_timeout(wdt_dev, wdt_dev->timeout); if (ret < 0) return ret; reg = ioread32(wdt_base + WDT_MODE); reg |= (WDT_MODE_DUAL_EN | WDT_MODE_IRQ_EN | WDT_MODE_EXRST_EN); reg &= ~(WDT_MODE_IRQ_LVL | WDT_MODE_EXT_POL_HIGH); reg |= (WDT_MODE_EN | WDT_MODE_KEY); iowrite32(reg, wdt_base + WDT_MODE); return 0; } void mtk_wdt_force_hw_wdt(void) { u32 reg; reg = ioread32(toprgu_base + WDT_MODE); reg &= ~(WDT_MODE_DUAL_EN | WDT_MODE_IRQ_EN | WDT_MODE_EXT_POL_HIGH); reg |= (WDT_MODE_EXRST_EN | WDT_MODE_EN | WDT_MODE_KEY); iowrite32(reg, toprgu_base + WDT_MODE); } static const struct watchdog_info mtk_wdt_info = { .identity = DRV_NAME, .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, }; static const struct watchdog_ops mtk_wdt_ops = { .owner = THIS_MODULE, .start = mtk_wdt_start, .stop = mtk_wdt_stop, .ping = mtk_wdt_ping, .set_timeout = mtk_wdt_set_timeout, }; #ifdef CONFIG_FIQ_GLUE static void wdt_fiq(void *arg, void *regs, void *svc_sp) { unsigned int wdt_mode_val; void __iomem *wdt_base = ((struct mtk_wdt_dev *)arg)->wdt_base; wdt_mode_val = __raw_readl(wdt_base + WDT_STATUS); writel(wdt_mode_val, wdt_base + WDT_NONRST_REG); aee_wdt_fiq_info(arg, regs, svc_sp); } #else #if 0 static void wdt_report_info(void) { struct task_struct *task; task = &init_task; pr_debug("Qwdt: -- watchdog time out\n"); for_each_process(task) { if (task->state == 0) { pr_debug("PID: %d, name: %s\n backtrace:\n", task->pid, task->comm); show_stack(task, NULL); pr_debug("\n"); } } pr_debug("backtrace of current task:\n"); show_stack(NULL, NULL); pr_debug("Qwdt: -- watchdog time out\n"); } static irqreturn_t mtk_wdt_isr(int irq, void *dev_id) { wdt_report_info(); WARN_ON(1); return IRQ_HANDLED; } #endif #endif static int mtk_wdt_probe(struct platform_device *pdev) { struct mtk_wdt_dev *mtk_wdt; struct resource *res; unsigned int tmp; int err; mtk_wdt = devm_kzalloc(&pdev->dev, sizeof(*mtk_wdt), GFP_KERNEL); if (!mtk_wdt) return -ENOMEM; platform_set_drvdata(pdev, mtk_wdt); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); mtk_wdt->wdt_base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(mtk_wdt->wdt_base)) return PTR_ERR(mtk_wdt->wdt_base); pr_info("MTK_WDT_NONRST_REG(%x)\n", __raw_readl(mtk_wdt->wdt_base + WDT_NONRST_REG)); #if 0 mtk_wdt->wdt_irq_id = irq_of_parse_and_map(pdev->dev.of_node, 0); if (mtk_wdt->wdt_irq_id == 0) { pr_err("RGU get IRQ ID failed\n"); return -ENODEV; } err = request_irq(mtk_wdt->wdt_irq_id, (irq_handler_t)mtk_wdt_isr, IRQF_TRIGGER_NONE, DRV_NAME, mtk_wdt); #endif #if 0 mtk_wdt->wdt_irq_id = get_hardware_irq(mtk_wdt->wdt_irq_id); err = request_fiq(mtk_wdt->wdt_irq_id, wdt_fiq, IRQF_TRIGGER_FALLING, mtk_wdt); if (err != 0) { pr_err("%s : failed to request irq (%d)\n", __func__, err); return err; } #endif toprgu_base = mtk_wdt->wdt_base; wdt_dev = &mtk_wdt->wdt_dev; mtk_wdt->wdt_dev.info = &mtk_wdt_info; mtk_wdt->wdt_dev.ops = &mtk_wdt_ops; mtk_wdt->wdt_dev.timeout = WDT_MAX_TIMEOUT; mtk_wdt->wdt_dev.max_timeout = WDT_MAX_TIMEOUT; mtk_wdt->wdt_dev.min_timeout = WDT_MIN_TIMEOUT; mtk_wdt->wdt_dev.parent = &pdev->dev; watchdog_init_timeout(&mtk_wdt->wdt_dev, timeout, &pdev->dev); watchdog_set_nowayout(&mtk_wdt->wdt_dev, nowayout); watchdog_set_drvdata(&mtk_wdt->wdt_dev, mtk_wdt); mtk_wdt_stop(&mtk_wdt->wdt_dev); err = watchdog_register_device(&mtk_wdt->wdt_dev); if (unlikely(err)) return err; mtk_wdt->restart_handler.notifier_call = mtk_reset_handler; mtk_wdt->restart_handler.priority = 128; if (arm_pm_restart) { dev_info(&pdev->dev, "register restart_handler on reboot_notifier_list for psci reset\n"); err = register_reboot_notifier(&mtk_wdt->restart_handler); if (err != 0) dev_warn(&pdev->dev, "cannot register reboot notifier (err=%d)\n", err); } else { err = register_restart_handler(&mtk_wdt->restart_handler); if (err != 0) dev_warn(&pdev->dev, "cannot register restart handler (err=%d)\n", err); } mtk_wdt->pm_handler.notifier_call = mtk_pm_handler; err = register_pm_notifier(&mtk_wdt->pm_handler); if (err) dev_warn(&pdev->dev, "[%s] failed to register WDT PM notifier %d\n", __func__, err); dev_info(&pdev->dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)\n", mtk_wdt->wdt_dev.timeout, nowayout); writel(WDT_REQ_MODE_KEY | (__raw_readl(mtk_wdt->wdt_base + WDT_REQ_MODE) & (~WDT_REQ_MODE_DEBUG_EN)), mtk_wdt->wdt_base + WDT_REQ_MODE); toprgu_register_reset_controller(pdev, WDT_SWSYSRST); /* * enable scpsys thermal and thermal_controller request, * and set to reset directly mode */ tmp = ioread32(mtk_wdt->wdt_base + WDT_REQ_MODE) | (1U << 18) | (1U << 0); tmp |= WDT_REQ_MODE_KEY; iowrite32(tmp, mtk_wdt->wdt_base + WDT_REQ_MODE); tmp = ioread32(mtk_wdt->wdt_base + WDT_REQ_IRQ_EN); tmp &= ~((1U << 18) | (1U << 0)); tmp |= WDT_REQ_IRQ_KEY; iowrite32(tmp, mtk_wdt->wdt_base + WDT_REQ_IRQ_EN); return 0; } static void mtk_wdt_shutdown(struct platform_device *pdev) { struct mtk_wdt_dev *mtk_wdt = platform_get_drvdata(pdev); if (watchdog_active(&mtk_wdt->wdt_dev)) mtk_wdt_stop(&mtk_wdt->wdt_dev); } static int mtk_wdt_remove(struct platform_device *pdev) { int err; struct mtk_wdt_dev *mtk_wdt = platform_get_drvdata(pdev); err = unregister_restart_handler(&mtk_wdt->restart_handler); if (err != 0) dev_err(&pdev->dev, "could not register toprgu reset controller: %d\n", err); watchdog_unregister_device(&mtk_wdt->wdt_dev); reset_controller_unregister(&mtk_wdt->reset_controller.rcdev); return 0; } #ifdef CONFIG_PM_SLEEP static int mtk_wdt_suspend(struct device *dev) { struct mtk_wdt_dev *mtk_wdt = dev_get_drvdata(dev); if (watchdog_active(&mtk_wdt->wdt_dev)) mtk_wdt_stop(&mtk_wdt->wdt_dev); return 0; } static int mtk_wdt_resume(struct device *dev) { struct mtk_wdt_dev *mtk_wdt = dev_get_drvdata(dev); if (watchdog_active(&mtk_wdt->wdt_dev)) { mtk_wdt_start(&mtk_wdt->wdt_dev); mtk_wdt_ping(&mtk_wdt->wdt_dev); } return 0; } #endif static const struct of_device_id mtk_wdt_dt_ids[] = { { .compatible = "mediatek,mt6589-wdt" }, { .compatible = "mediatek,mt8163-rgu" }, { .compatible = "mediatek,mt8173-wdt" }, { .compatible = "mediatek,mt8167-wdt" }, { .compatible = "mediatek,mt8168-rgu" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, mtk_wdt_dt_ids); static const struct dev_pm_ops mtk_wdt_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(mtk_wdt_suspend, mtk_wdt_resume) }; static struct platform_driver mtk_wdt_driver = { .probe = mtk_wdt_probe, .remove = mtk_wdt_remove, .shutdown = mtk_wdt_shutdown, .driver = { .name = DRV_NAME, .pm = &mtk_wdt_pm_ops, .of_match_table = mtk_wdt_dt_ids, }, }; module_platform_driver(mtk_wdt_driver); static int wk_proc_cmd_read(struct seq_file *s, void *v) { unsigned int enabled = 1; if (0 == (ioread32(toprgu_base + WDT_MODE) & WDT_MODE_EN)) enabled = 0; seq_printf(s, "enabled timeout\n%-4d %-8d\n", enabled, wdt_dev->timeout); return 0; } static int wk_proc_cmd_open(struct inode *inode, struct file *file) { return single_open(file, wk_proc_cmd_read, NULL); } static ssize_t wk_proc_cmd_write (struct file *file, const char *buf, size_t count, loff_t *data) { int ret; int enable; unsigned int timeout; char wk_cmd_buf[256]; if (count == 0) return -1; if (count > 255) count = 255; ret = (int)copy_from_user(wk_cmd_buf, buf, count); if (ret < 0) return -1; wk_cmd_buf[count] = '\0'; pr_debug("Write %s\n", wk_cmd_buf); ret = sscanf(wk_cmd_buf, "%d %d", &enable, &timeout); if (ret != 2) pr_debug("%s: expect 2 numbers\n", __func__); pr_debug("[WDK] enable=%d timeout=%d\n", enable, timeout); if (timeout > 20 && timeout <= WDT_MAX_TIMEOUT) { wdt_dev->timeout = timeout; ret = mtk_wdt_set_timeout(wdt_dev, wdt_dev->timeout); if (ret < 0) return ret; } else { pr_err("[WDK] The timeout(%d) should bigger than 20 and not bigger than %d\n", timeout, WDT_MAX_TIMEOUT); } if (enable == 1) { ret = mtk_wdt_start(wdt_dev); if (ret < 0) return ret; set_bit(WDOG_ACTIVE, &wdt_dev->status); pr_info("[WDK] enable wdt\n"); } else if (enable == 0) { mtk_wdt_stop(wdt_dev); clear_bit(WDOG_ACTIVE, &wdt_dev->status); pr_info("[WDK] disable wdt\n"); } return count; } static const struct file_operations wk_proc_cmd_fops = { .owner = THIS_MODULE, .open = wk_proc_cmd_open, .read = seq_read, .write = wk_proc_cmd_write, .llseek = seq_lseek, .release = single_release, }; static int __init wk_proc_init(void) { struct proc_dir_entry *de = proc_create ("wdk", 0660, NULL, &wk_proc_cmd_fops); if (de == 0) pr_err("[%s]: create /proc/wdk failed\n", __func__); pr_debug("[WDK] Initialize proc\n"); return 0; } late_initcall(wk_proc_init); module_param(timeout, uint, 0); MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds"); module_param(nowayout, bool, 0); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Matthias Brugger "); MODULE_DESCRIPTION("Mediatek WatchDog Timer Driver"); MODULE_VERSION(DRV_VERSION);