| /* |
| * Mediatek Watchdog Driver |
| * |
| * Copyright (C) 2014 Matthias Brugger |
| * |
| * Matthias Brugger <matthias.bgg@gmail.com> |
| * |
| * 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 <linux/err.h> |
| #include <linux/init.h> |
| #include <linux/io.h> |
| #include <linux/interrupt.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/init.h> |
| #include <linux/proc_fs.h> |
| #include <linux/uaccess.h> |
| #include <linux/of.h> |
| #include <linux/of_irq.h> |
| #include <linux/platform_device.h> |
| #ifdef CONFIG_FIQ_GLUE |
| #include <linux/irqchip/mtk-gic-extend.h> |
| #include <mt-plat/aee.h> |
| #endif |
| #include <linux/types.h> |
| #include <linux/watchdog.h> |
| #include <linux/notifier.h> |
| #include <linux/reboot.h> |
| #include <linux/delay.h> |
| #include <linux/reset-controller.h> |
| #include <linux/reset.h> |
| #include <linux/sched.h> |
| #include <asm/system_misc.h> |
| #ifdef CONFIG_MT6397_MISC |
| #include <linux/mfd/mt6397/rtc_misc.h> |
| #endif |
| #ifdef CONFIG_MTK_RTC |
| #include <../misc/mediatek/include/mt-plat/mtk_rtc.h> |
| #endif |
| #include <linux/suspend.h> |
| |
| #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 <matthias.bgg@gmail.com>"); |
| MODULE_DESCRIPTION("Mediatek WatchDog Timer Driver"); |
| MODULE_VERSION(DRV_VERSION); |