diff --git a/arch/arm64/configs/meson64_defconfig b/arch/arm64/configs/meson64_defconfig index aba75d856e40..e54033ee70d2 100644 --- a/arch/arm64/configs/meson64_defconfig +++ b/arch/arm64/configs/meson64_defconfig @@ -240,6 +240,8 @@ CONFIG_AMLOGIC_AUDIO_DSP=y CONFIG_AMLOGIC_GX_SUSPEND=y CONFIG_AMLOGIC_JTAG=y CONFIG_AMLOGIC_JTAG_MESON=y +CONFIG_AMLOGIC_WDT=y +CONFIG_AMLOGIC_WDT_MESON=y CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" CONFIG_DEVTMPFS=y CONFIG_DEVTMPFS_MOUNT=y diff --git a/drivers/amlogic/Kconfig b/drivers/amlogic/Kconfig index 25aca809f6ed..9a0f3b502913 100644 --- a/drivers/amlogic/Kconfig +++ b/drivers/amlogic/Kconfig @@ -75,5 +75,7 @@ source "drivers/amlogic/pm/Kconfig" source "drivers/amlogic/jtag/Kconfig" +source "drivers/amlogic/watchdog/Kconfig" + endmenu endif diff --git a/drivers/amlogic/Makefile b/drivers/amlogic/Makefile index cc07d573e654..8d0849de17fd 100644 --- a/drivers/amlogic/Makefile +++ b/drivers/amlogic/Makefile @@ -71,3 +71,5 @@ obj-$(CONFIG_AMLOGIC_GX_SUSPEND) += pm/ obj-$(CONFIG_AMLOGIC_JTAG) += jtag/ +obj-$(CONFIG_AMLOGIC_WDT) += watchdog/ + diff --git a/drivers/amlogic/watchdog/Kconfig b/drivers/amlogic/watchdog/Kconfig new file mode 100644 index 000000000000..22dc977bdd97 --- /dev/null +++ b/drivers/amlogic/watchdog/Kconfig @@ -0,0 +1,21 @@ +# +# watchdog configuration +# + + +menuconfig AMLOGIC_WDT + bool "Amlogic Watchdog Support" + select WATCHDOG + select WATCHDOG_CORE + default n + help + say y to enable Amlogic watchdog driver. + + +if AMLOGIC_WDT +config AMLOGIC_WDT_MESON + bool "Amlogic Meson Watchdog" + default n + help + say y to enable Amlogic meson watchdog driver. +endif diff --git a/drivers/amlogic/watchdog/Makefile b/drivers/amlogic/watchdog/Makefile new file mode 100644 index 000000000000..381fbee2a4a9 --- /dev/null +++ b/drivers/amlogic/watchdog/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for MHL +# + +obj-$(CONFIG_AMLOGIC_WDT_MESON) += meson_wdt.o + diff --git a/drivers/amlogic/watchdog/meson_wdt.c b/drivers/amlogic/watchdog/meson_wdt.c new file mode 100644 index 000000000000..ab143fbcfe83 --- /dev/null +++ b/drivers/amlogic/watchdog/meson_wdt.c @@ -0,0 +1,406 @@ +/* + * drivers/amlogic/watchdog/meson_wdt.c + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef pr_fmt +#define pr_fmt(fmt) "aml-wdt: %s: " fmt, __func__ + +#define CTRL 0x0 +#define CTRL1 0x4 +#define TCNT 0x8 +#define RESET 0xc + +struct aml_wdt_dev { + unsigned int min_timeout, max_timeout; + unsigned int default_timeout, reset_watchdog_time, shutdown_timeout; + unsigned int firmware_timeout, suspend_timeout, timeout; + unsigned int one_second; + struct device *dev; + struct mutex lock; + unsigned int reset_watchdog_method; + struct delayed_work boot_queue; + void __iomem *reg_base; + struct notifier_block pm_notifier; + struct notifier_block reboot_notifier; +}; + + +static void aml_update_bits(void __iomem *reg, unsigned int mask, + unsigned int val) +{ + unsigned int tmp, orig; + + orig = readl(reg); + tmp = orig & ~mask; + tmp |= val & mask; + writel(tmp, reg); +} + +static void enable_watchdog(struct aml_wdt_dev *awdtv) +{ + aml_update_bits(awdtv->reg_base + CTRL, 0x1<<18, 0x1<<18); +} + +static void disable_watchdog(struct aml_wdt_dev *awdtv) +{ + aml_update_bits(awdtv->reg_base + CTRL, 0x1<<18, 0<<18); +} + +static void reset_watchdog(struct aml_wdt_dev *awdtv) +{ + writel(0x0, awdtv->reg_base + RESET); +} + +static void set_watchdog_cnt(struct aml_wdt_dev *awdtv, unsigned int cnt) +{ + writel((cnt & 0xffff), awdtv->reg_base + TCNT); +} + +static unsigned int read_watchdog_tcnt(struct aml_wdt_dev *awdtv) +{ + return (readl(awdtv->reg_base + TCNT) & 0xffff0000) >> 16; +} + +static int aml_wdt_start(struct watchdog_device *wdog) +{ + struct aml_wdt_dev *wdev = watchdog_get_drvdata(wdog); + + mutex_lock(&wdev->lock); + if (wdog->timeout == 0xffffffff) + set_watchdog_cnt(wdev, wdev->default_timeout * + wdev->one_second); + else + set_watchdog_cnt(wdev, wdog->timeout * wdev->one_second); + enable_watchdog(wdev); + mutex_unlock(&wdev->lock); +#if 0 + if (wdev->boot_queue) + cancel_delayed_work(&wdev->boot_queue); +#endif + return 0; +} + +static int aml_wdt_stop(struct watchdog_device *wdog) +{ + struct aml_wdt_dev *wdev = watchdog_get_drvdata(wdog); + + mutex_lock(&wdev->lock); + disable_watchdog(wdev); + mutex_unlock(&wdev->lock); + return 0; +} + +static int aml_wdt_ping(struct watchdog_device *wdog) +{ + struct aml_wdt_dev *wdev = watchdog_get_drvdata(wdog); + + mutex_lock(&wdev->lock); + reset_watchdog(wdev); + mutex_unlock(&wdev->lock); + + return 0; +} + +static int aml_wdt_set_timeout(struct watchdog_device *wdog, + unsigned int timeout) +{ + struct aml_wdt_dev *wdev = watchdog_get_drvdata(wdog); + + mutex_lock(&wdev->lock); + set_watchdog_cnt(wdev, timeout * wdev->one_second); + wdog->timeout = timeout; + wdev->timeout = timeout; + mutex_unlock(&wdev->lock); + + return 0; +} + +unsigned int aml_wdt_get_timeleft(struct watchdog_device *wdog) +{ + struct aml_wdt_dev *wdev = watchdog_get_drvdata(wdog); + unsigned int timeleft; + + mutex_lock(&wdev->lock); + timeleft = read_watchdog_tcnt(wdev); + mutex_unlock(&wdev->lock); + return timeleft/wdev->one_second; +} + +static void boot_moniter_work(struct work_struct *work) +{ + struct aml_wdt_dev *wdev = container_of(work, struct aml_wdt_dev, + boot_queue.work); + reset_watchdog(wdev); + mod_delayed_work(system_freezable_wq, &wdev->boot_queue, + round_jiffies(msecs_to_jiffies(wdev->reset_watchdog_time*1000))); +} + +static const struct watchdog_info aml_wdt_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, + .identity = "aml Watchdog", +}; + +static const struct watchdog_ops aml_wdt_ops = { + .owner = THIS_MODULE, + .start = aml_wdt_start, + .stop = aml_wdt_stop, + .ping = aml_wdt_ping, + .set_timeout = aml_wdt_set_timeout, + .get_timeleft = aml_wdt_get_timeleft, +}; + +void aml_init_pdata(struct aml_wdt_dev *wdev) +{ + int ret; + struct clk *clk; + + ret = of_property_read_u32(wdev->dev->of_node, "default_timeout", + &wdev->default_timeout); + if (ret) { + dev_err(wdev->dev, + "dt probe default_timeout failed: %d\n", ret); + wdev->default_timeout = 5; + } + ret = of_property_read_u32(wdev->dev->of_node, "reset_watchdog_method", + &wdev->reset_watchdog_method); + if (ret) { + dev_err(wdev->dev, + "dt probe reset_watchdog_method failed: %d\n", ret); + wdev->reset_watchdog_method = 1; + } + ret = of_property_read_u32(wdev->dev->of_node, "reset_watchdog_time", + &wdev->reset_watchdog_time); + if (ret) { + dev_err(wdev->dev, + "dt probe reset_watchdog_time failed: %d\n", ret); + wdev->reset_watchdog_time = 2; + } + + ret = of_property_read_u32(wdev->dev->of_node, "shutdown_timeout", + &wdev->shutdown_timeout); + if (ret) { + dev_err(wdev->dev, + "dt probe shutdown_timeout failed: %d\n", ret); + wdev->shutdown_timeout = 10; + } + + ret = of_property_read_u32(wdev->dev->of_node, "firmware_timeout", + &wdev->firmware_timeout); + if (ret) { + dev_err(wdev->dev, + "dt probe firmware_timeout failed: %d\n", ret); + wdev->firmware_timeout = 6; + } + + ret = of_property_read_u32(wdev->dev->of_node, "suspend_timeout", + &wdev->suspend_timeout); + if (ret) { + dev_err(wdev->dev, + "dt probe suspend_timeout failed: %d\n", ret); + wdev->suspend_timeout = 6; + } + + + wdev->reg_base = of_iomap(wdev->dev->of_node, 0); + clk = clk_get(wdev->dev, NULL); + aml_update_bits(wdev->reg_base + CTRL, 0x3<<21, 0x3<<21); + aml_update_bits(wdev->reg_base + CTRL, 0x3<<24, 0x3<<24); + aml_update_bits(wdev->reg_base + CTRL, 0x3ffff, 24000); + + wdev->one_second = 1000; + wdev->max_timeout = 0xffff/1000; + wdev->min_timeout = 1; +} + +static int aml_wtd_pm_notify(struct notifier_block *nb, unsigned long event, + void *dummy) +{ + struct aml_wdt_dev *wdev; + + wdev = container_of(nb, struct aml_wdt_dev, pm_notifier); + + if (event == PM_SUSPEND_PREPARE) { + disable_watchdog(wdev); + pr_info("disable watchdog\n"); + } + if (event == PM_POST_SUSPEND) { + enable_watchdog(wdev); + pr_info("enable watchdog\n"); + } + return NOTIFY_OK; +} + +static int aml_wtd_reboot_notify(struct notifier_block *nb, + unsigned long event, + void *dummy) +{ + struct aml_wdt_dev *wdev; + + wdev = container_of(nb, struct aml_wdt_dev, reboot_notifier); + + disable_watchdog(wdev); + pr_info("disable watchdog\n"); + return NOTIFY_OK; +} + + +static struct notifier_block aml_wdt_pm_notifier = { + .notifier_call = aml_wtd_pm_notify, +}; + +static struct notifier_block aml_wdt_reboot_notifier = { + .notifier_call = aml_wtd_reboot_notify, +}; + +static int aml_wdt_probe(struct platform_device *pdev) +{ + struct watchdog_device *aml_wdt; + struct aml_wdt_dev *wdev; + int ret; + + aml_wdt = devm_kzalloc(&pdev->dev, sizeof(*aml_wdt), GFP_KERNEL); + if (!aml_wdt) + return -ENOMEM; + + wdev = devm_kzalloc(&pdev->dev, sizeof(*wdev), GFP_KERNEL); + if (!wdev) + return -ENOMEM; + wdev->dev = &pdev->dev; + mutex_init(&wdev->lock); + aml_init_pdata(wdev); + + aml_wdt->info = &aml_wdt_info; + aml_wdt->ops = &aml_wdt_ops; + aml_wdt->min_timeout = wdev->min_timeout; + aml_wdt->max_timeout = wdev->max_timeout; + aml_wdt->timeout = 0xffffffff; + wdev->timeout = 0xffffffff; + + watchdog_set_drvdata(aml_wdt, wdev); + platform_set_drvdata(pdev, aml_wdt); + if (wdev->reset_watchdog_method == 1) { + + INIT_DELAYED_WORK(&wdev->boot_queue, boot_moniter_work); + mod_delayed_work(system_freezable_wq, &wdev->boot_queue, + round_jiffies(msecs_to_jiffies(wdev->reset_watchdog_time*1000))); + enable_watchdog(wdev); + set_watchdog_cnt(wdev, + wdev->default_timeout * wdev->one_second); + dev_info(wdev->dev, "creat work queue for watch dog\n"); + } + ret = watchdog_register_device(aml_wdt); + if (ret) + return ret; + + wdev->reboot_notifier = aml_wdt_pm_notifier; + wdev->reboot_notifier = aml_wdt_reboot_notifier; + register_pm_notifier(&aml_wdt_pm_notifier); + register_reboot_notifier(&aml_wdt_reboot_notifier); + dev_info(wdev->dev, "AML Watchdog Timer probed done\n"); + + return 0; +} + +static void aml_wdt_shutdown(struct platform_device *pdev) +{ + struct watchdog_device *wdog = platform_get_drvdata(pdev); + struct aml_wdt_dev *wdev = watchdog_get_drvdata(wdog); + + if (wdev->reset_watchdog_method == 1) + cancel_delayed_work(&wdev->boot_queue); + disable_watchdog(wdev); +} + +static int aml_wdt_remove(struct platform_device *pdev) +{ + struct watchdog_device *wdog = platform_get_drvdata(pdev); + + aml_wdt_stop(wdog); + return 0; +} + +#ifdef CONFIG_PM +static int aml_wdt_suspend(struct platform_device *pdev, pm_message_t state) +{ + return 0; +} + +static int aml_wdt_resume(struct platform_device *pdev) +{ + return 0; +} + +#else +#define aml_wdt_suspend NULL +#define aml_wdt_resume NULL +#endif + +static const struct of_device_id aml_wdt_of_match[] = { + { .compatible = "amlogic, gx-wdt", }, + {}, +}; +MODULE_DEVICE_TABLE(of, aml_wdt_of_match); + +static struct platform_driver aml_wdt_driver = { + .probe = aml_wdt_probe, + .remove = aml_wdt_remove, + .shutdown = aml_wdt_shutdown, + .suspend = aml_wdt_suspend, + .resume = aml_wdt_resume, + .driver = { + .owner = THIS_MODULE, + .name = "aml_wdt", + .of_match_table = aml_wdt_of_match, + }, +}; + +static int __init aml_wdt_driver_init(void) +{ + return platform_driver_register(&(aml_wdt_driver)); +} + +module_init(aml_wdt_driver_init); + +static void __exit aml_wdt_driver_exit(void) +{ + platform_driver_unregister(&(aml_wdt_driver)); +} + +module_exit(aml_wdt_driver_exit); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:aml_wdt"); + +