141b630f4SAnson Huang // SPDX-License-Identifier: GPL-2.0 241b630f4SAnson Huang /* 341b630f4SAnson Huang * Copyright 2019 NXP. 441b630f4SAnson Huang */ 541b630f4SAnson Huang 641b630f4SAnson Huang #include <linux/clk.h> 741b630f4SAnson Huang #include <linux/io.h> 874394946SAnson Huang #include <linux/iopoll.h> 941b630f4SAnson Huang #include <linux/kernel.h> 1041b630f4SAnson Huang #include <linux/module.h> 1141b630f4SAnson Huang #include <linux/of.h> 1241b630f4SAnson Huang #include <linux/platform_device.h> 1341b630f4SAnson Huang #include <linux/reboot.h> 1441b630f4SAnson Huang #include <linux/watchdog.h> 1541b630f4SAnson Huang 1641b630f4SAnson Huang #define WDOG_CS 0x0 1741b630f4SAnson Huang #define WDOG_CS_CMD32EN BIT(13) 1841b630f4SAnson Huang #define WDOG_CS_ULK BIT(11) 1941b630f4SAnson Huang #define WDOG_CS_RCS BIT(10) 20eccb7fe5SFabio Estevam #define LPO_CLK 0x1 21eccb7fe5SFabio Estevam #define LPO_CLK_SHIFT 8 22eccb7fe5SFabio Estevam #define WDOG_CS_CLK (LPO_CLK << LPO_CLK_SHIFT) 2341b630f4SAnson Huang #define WDOG_CS_EN BIT(7) 2441b630f4SAnson Huang #define WDOG_CS_UPDATE BIT(5) 25*0cfbe179SAnson Huang #define WDOG_CS_WAIT BIT(1) 26*0cfbe179SAnson Huang #define WDOG_CS_STOP BIT(0) 2741b630f4SAnson Huang 2841b630f4SAnson Huang #define WDOG_CNT 0x4 2941b630f4SAnson Huang #define WDOG_TOVAL 0x8 3041b630f4SAnson Huang 3141b630f4SAnson Huang #define REFRESH_SEQ0 0xA602 3241b630f4SAnson Huang #define REFRESH_SEQ1 0xB480 3341b630f4SAnson Huang #define REFRESH ((REFRESH_SEQ1 << 16) | REFRESH_SEQ0) 3441b630f4SAnson Huang 3541b630f4SAnson Huang #define UNLOCK_SEQ0 0xC520 3641b630f4SAnson Huang #define UNLOCK_SEQ1 0xD928 3741b630f4SAnson Huang #define UNLOCK ((UNLOCK_SEQ1 << 16) | UNLOCK_SEQ0) 3841b630f4SAnson Huang 3941b630f4SAnson Huang #define DEFAULT_TIMEOUT 60 4041b630f4SAnson Huang #define MAX_TIMEOUT 128 4141b630f4SAnson Huang #define WDOG_CLOCK_RATE 1000 4274394946SAnson Huang #define WDOG_WAIT_TIMEOUT 20 4341b630f4SAnson Huang 4441b630f4SAnson Huang static bool nowayout = WATCHDOG_NOWAYOUT; 4541b630f4SAnson Huang module_param(nowayout, bool, 0000); 4641b630f4SAnson Huang MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 4741b630f4SAnson Huang __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 4841b630f4SAnson Huang 4941b630f4SAnson Huang struct imx7ulp_wdt_device { 5041b630f4SAnson Huang struct watchdog_device wdd; 5141b630f4SAnson Huang void __iomem *base; 5241b630f4SAnson Huang struct clk *clk; 5341b630f4SAnson Huang }; 5441b630f4SAnson Huang 5574394946SAnson Huang static int imx7ulp_wdt_wait(void __iomem *base, u32 mask) 5674394946SAnson Huang { 5774394946SAnson Huang u32 val = readl(base + WDOG_CS); 5874394946SAnson Huang 5974394946SAnson Huang if (!(val & mask) && readl_poll_timeout_atomic(base + WDOG_CS, val, 6074394946SAnson Huang val & mask, 0, 6174394946SAnson Huang WDOG_WAIT_TIMEOUT)) 6274394946SAnson Huang return -ETIMEDOUT; 6374394946SAnson Huang 6474394946SAnson Huang return 0; 6574394946SAnson Huang } 6674394946SAnson Huang 6774394946SAnson Huang static int imx7ulp_wdt_enable(struct watchdog_device *wdog, bool enable) 6841b630f4SAnson Huang { 69747d88a1SFabio Estevam struct imx7ulp_wdt_device *wdt = watchdog_get_drvdata(wdog); 7041b630f4SAnson Huang 71747d88a1SFabio Estevam u32 val = readl(wdt->base + WDOG_CS); 7274394946SAnson Huang int ret; 73747d88a1SFabio Estevam 7474394946SAnson Huang local_irq_disable(); 75747d88a1SFabio Estevam writel(UNLOCK, wdt->base + WDOG_CNT); 7674394946SAnson Huang ret = imx7ulp_wdt_wait(wdt->base, WDOG_CS_ULK); 7774394946SAnson Huang if (ret) 7874394946SAnson Huang goto enable_out; 7941b630f4SAnson Huang if (enable) 80747d88a1SFabio Estevam writel(val | WDOG_CS_EN, wdt->base + WDOG_CS); 8141b630f4SAnson Huang else 82747d88a1SFabio Estevam writel(val & ~WDOG_CS_EN, wdt->base + WDOG_CS); 8374394946SAnson Huang imx7ulp_wdt_wait(wdt->base, WDOG_CS_RCS); 8474394946SAnson Huang 8574394946SAnson Huang enable_out: 8674394946SAnson Huang local_irq_enable(); 8774394946SAnson Huang 8874394946SAnson Huang return ret; 8941b630f4SAnson Huang } 9041b630f4SAnson Huang 91c37e3581SFabio Estevam static bool imx7ulp_wdt_is_enabled(void __iomem *base) 9241b630f4SAnson Huang { 9341b630f4SAnson Huang u32 val = readl(base + WDOG_CS); 9441b630f4SAnson Huang 9541b630f4SAnson Huang return val & WDOG_CS_EN; 9641b630f4SAnson Huang } 9741b630f4SAnson Huang 9841b630f4SAnson Huang static int imx7ulp_wdt_ping(struct watchdog_device *wdog) 9941b630f4SAnson Huang { 10041b630f4SAnson Huang struct imx7ulp_wdt_device *wdt = watchdog_get_drvdata(wdog); 10141b630f4SAnson Huang 10241b630f4SAnson Huang writel(REFRESH, wdt->base + WDOG_CNT); 10341b630f4SAnson Huang 10441b630f4SAnson Huang return 0; 10541b630f4SAnson Huang } 10641b630f4SAnson Huang 10741b630f4SAnson Huang static int imx7ulp_wdt_start(struct watchdog_device *wdog) 10841b630f4SAnson Huang { 10974394946SAnson Huang return imx7ulp_wdt_enable(wdog, true); 11041b630f4SAnson Huang } 11141b630f4SAnson Huang 11241b630f4SAnson Huang static int imx7ulp_wdt_stop(struct watchdog_device *wdog) 11341b630f4SAnson Huang { 11474394946SAnson Huang return imx7ulp_wdt_enable(wdog, false); 11541b630f4SAnson Huang } 11641b630f4SAnson Huang 11741b630f4SAnson Huang static int imx7ulp_wdt_set_timeout(struct watchdog_device *wdog, 11841b630f4SAnson Huang unsigned int timeout) 11941b630f4SAnson Huang { 12041b630f4SAnson Huang struct imx7ulp_wdt_device *wdt = watchdog_get_drvdata(wdog); 12141b630f4SAnson Huang u32 val = WDOG_CLOCK_RATE * timeout; 12274394946SAnson Huang int ret; 12341b630f4SAnson Huang 12474394946SAnson Huang local_irq_disable(); 12541b630f4SAnson Huang writel(UNLOCK, wdt->base + WDOG_CNT); 12674394946SAnson Huang ret = imx7ulp_wdt_wait(wdt->base, WDOG_CS_ULK); 12774394946SAnson Huang if (ret) 12874394946SAnson Huang goto timeout_out; 12941b630f4SAnson Huang writel(val, wdt->base + WDOG_TOVAL); 13074394946SAnson Huang imx7ulp_wdt_wait(wdt->base, WDOG_CS_RCS); 13141b630f4SAnson Huang 13241b630f4SAnson Huang wdog->timeout = timeout; 13341b630f4SAnson Huang 13474394946SAnson Huang timeout_out: 13574394946SAnson Huang local_irq_enable(); 13674394946SAnson Huang 13774394946SAnson Huang return ret; 13841b630f4SAnson Huang } 13941b630f4SAnson Huang 1406083ab7bSFabio Estevam static int imx7ulp_wdt_restart(struct watchdog_device *wdog, 1416083ab7bSFabio Estevam unsigned long action, void *data) 1426083ab7bSFabio Estevam { 1436083ab7bSFabio Estevam struct imx7ulp_wdt_device *wdt = watchdog_get_drvdata(wdog); 14474394946SAnson Huang int ret; 1456083ab7bSFabio Estevam 14674394946SAnson Huang ret = imx7ulp_wdt_enable(wdog, true); 14774394946SAnson Huang if (ret) 14874394946SAnson Huang return ret; 14974394946SAnson Huang 15074394946SAnson Huang ret = imx7ulp_wdt_set_timeout(&wdt->wdd, 1); 15174394946SAnson Huang if (ret) 15274394946SAnson Huang return ret; 1536083ab7bSFabio Estevam 1546083ab7bSFabio Estevam /* wait for wdog to fire */ 1556083ab7bSFabio Estevam while (true) 1566083ab7bSFabio Estevam ; 1576083ab7bSFabio Estevam 1586083ab7bSFabio Estevam return NOTIFY_DONE; 1596083ab7bSFabio Estevam } 1606083ab7bSFabio Estevam 16141b630f4SAnson Huang static const struct watchdog_ops imx7ulp_wdt_ops = { 16241b630f4SAnson Huang .owner = THIS_MODULE, 16341b630f4SAnson Huang .start = imx7ulp_wdt_start, 16441b630f4SAnson Huang .stop = imx7ulp_wdt_stop, 16541b630f4SAnson Huang .ping = imx7ulp_wdt_ping, 16641b630f4SAnson Huang .set_timeout = imx7ulp_wdt_set_timeout, 1676083ab7bSFabio Estevam .restart = imx7ulp_wdt_restart, 16841b630f4SAnson Huang }; 16941b630f4SAnson Huang 17041b630f4SAnson Huang static const struct watchdog_info imx7ulp_wdt_info = { 17141b630f4SAnson Huang .identity = "i.MX7ULP watchdog timer", 17241b630f4SAnson Huang .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | 17341b630f4SAnson Huang WDIOF_MAGICCLOSE, 17441b630f4SAnson Huang }; 17541b630f4SAnson Huang 17674394946SAnson Huang static int imx7ulp_wdt_init(void __iomem *base, unsigned int timeout) 17741b630f4SAnson Huang { 17841b630f4SAnson Huang u32 val; 17974394946SAnson Huang int ret; 18041b630f4SAnson Huang 18174394946SAnson Huang local_irq_disable(); 18241b630f4SAnson Huang /* unlock the wdog for reconfiguration */ 18341b630f4SAnson Huang writel_relaxed(UNLOCK_SEQ0, base + WDOG_CNT); 18441b630f4SAnson Huang writel_relaxed(UNLOCK_SEQ1, base + WDOG_CNT); 18574394946SAnson Huang ret = imx7ulp_wdt_wait(base, WDOG_CS_ULK); 18674394946SAnson Huang if (ret) 18774394946SAnson Huang goto init_out; 18841b630f4SAnson Huang 18941b630f4SAnson Huang /* set an initial timeout value in TOVAL */ 19041b630f4SAnson Huang writel(timeout, base + WDOG_TOVAL); 19141b630f4SAnson Huang /* enable 32bit command sequence and reconfigure */ 192*0cfbe179SAnson Huang val = WDOG_CS_CMD32EN | WDOG_CS_CLK | WDOG_CS_UPDATE | 193*0cfbe179SAnson Huang WDOG_CS_WAIT | WDOG_CS_STOP; 19441b630f4SAnson Huang writel(val, base + WDOG_CS); 19574394946SAnson Huang imx7ulp_wdt_wait(base, WDOG_CS_RCS); 19674394946SAnson Huang 19774394946SAnson Huang init_out: 19874394946SAnson Huang local_irq_enable(); 19974394946SAnson Huang 20074394946SAnson Huang return ret; 20141b630f4SAnson Huang } 20241b630f4SAnson Huang 20341b630f4SAnson Huang static void imx7ulp_wdt_action(void *data) 20441b630f4SAnson Huang { 20541b630f4SAnson Huang clk_disable_unprepare(data); 20641b630f4SAnson Huang } 20741b630f4SAnson Huang 20841b630f4SAnson Huang static int imx7ulp_wdt_probe(struct platform_device *pdev) 20941b630f4SAnson Huang { 21041b630f4SAnson Huang struct imx7ulp_wdt_device *imx7ulp_wdt; 21141b630f4SAnson Huang struct device *dev = &pdev->dev; 21241b630f4SAnson Huang struct watchdog_device *wdog; 21341b630f4SAnson Huang int ret; 21441b630f4SAnson Huang 21541b630f4SAnson Huang imx7ulp_wdt = devm_kzalloc(dev, sizeof(*imx7ulp_wdt), GFP_KERNEL); 21641b630f4SAnson Huang if (!imx7ulp_wdt) 21741b630f4SAnson Huang return -ENOMEM; 21841b630f4SAnson Huang 21941b630f4SAnson Huang platform_set_drvdata(pdev, imx7ulp_wdt); 22041b630f4SAnson Huang 22141b630f4SAnson Huang imx7ulp_wdt->base = devm_platform_ioremap_resource(pdev, 0); 22241b630f4SAnson Huang if (IS_ERR(imx7ulp_wdt->base)) 22341b630f4SAnson Huang return PTR_ERR(imx7ulp_wdt->base); 22441b630f4SAnson Huang 22541b630f4SAnson Huang imx7ulp_wdt->clk = devm_clk_get(dev, NULL); 22641b630f4SAnson Huang if (IS_ERR(imx7ulp_wdt->clk)) { 22741b630f4SAnson Huang dev_err(dev, "Failed to get watchdog clock\n"); 22841b630f4SAnson Huang return PTR_ERR(imx7ulp_wdt->clk); 22941b630f4SAnson Huang } 23041b630f4SAnson Huang 23141b630f4SAnson Huang ret = clk_prepare_enable(imx7ulp_wdt->clk); 23241b630f4SAnson Huang if (ret) 23341b630f4SAnson Huang return ret; 23441b630f4SAnson Huang 23541b630f4SAnson Huang ret = devm_add_action_or_reset(dev, imx7ulp_wdt_action, imx7ulp_wdt->clk); 23641b630f4SAnson Huang if (ret) 23741b630f4SAnson Huang return ret; 23841b630f4SAnson Huang 23941b630f4SAnson Huang wdog = &imx7ulp_wdt->wdd; 24041b630f4SAnson Huang wdog->info = &imx7ulp_wdt_info; 24141b630f4SAnson Huang wdog->ops = &imx7ulp_wdt_ops; 24241b630f4SAnson Huang wdog->min_timeout = 1; 24341b630f4SAnson Huang wdog->max_timeout = MAX_TIMEOUT; 24441b630f4SAnson Huang wdog->parent = dev; 24541b630f4SAnson Huang wdog->timeout = DEFAULT_TIMEOUT; 24641b630f4SAnson Huang 24741b630f4SAnson Huang watchdog_init_timeout(wdog, 0, dev); 24841b630f4SAnson Huang watchdog_stop_on_reboot(wdog); 24941b630f4SAnson Huang watchdog_stop_on_unregister(wdog); 25041b630f4SAnson Huang watchdog_set_drvdata(wdog, imx7ulp_wdt); 25174394946SAnson Huang ret = imx7ulp_wdt_init(imx7ulp_wdt->base, wdog->timeout * WDOG_CLOCK_RATE); 25274394946SAnson Huang if (ret) 25374394946SAnson Huang return ret; 25441b630f4SAnson Huang 25541b630f4SAnson Huang return devm_watchdog_register_device(dev, wdog); 25641b630f4SAnson Huang } 25741b630f4SAnson Huang 25841b630f4SAnson Huang static int __maybe_unused imx7ulp_wdt_suspend(struct device *dev) 25941b630f4SAnson Huang { 26041b630f4SAnson Huang struct imx7ulp_wdt_device *imx7ulp_wdt = dev_get_drvdata(dev); 26141b630f4SAnson Huang 26241b630f4SAnson Huang if (watchdog_active(&imx7ulp_wdt->wdd)) 26341b630f4SAnson Huang imx7ulp_wdt_stop(&imx7ulp_wdt->wdd); 26441b630f4SAnson Huang 26541b630f4SAnson Huang clk_disable_unprepare(imx7ulp_wdt->clk); 26641b630f4SAnson Huang 26741b630f4SAnson Huang return 0; 26841b630f4SAnson Huang } 26941b630f4SAnson Huang 27041b630f4SAnson Huang static int __maybe_unused imx7ulp_wdt_resume(struct device *dev) 27141b630f4SAnson Huang { 27241b630f4SAnson Huang struct imx7ulp_wdt_device *imx7ulp_wdt = dev_get_drvdata(dev); 27341b630f4SAnson Huang u32 timeout = imx7ulp_wdt->wdd.timeout * WDOG_CLOCK_RATE; 27441b630f4SAnson Huang int ret; 27541b630f4SAnson Huang 27641b630f4SAnson Huang ret = clk_prepare_enable(imx7ulp_wdt->clk); 27741b630f4SAnson Huang if (ret) 27841b630f4SAnson Huang return ret; 27941b630f4SAnson Huang 28041b630f4SAnson Huang if (imx7ulp_wdt_is_enabled(imx7ulp_wdt->base)) 28141b630f4SAnson Huang imx7ulp_wdt_init(imx7ulp_wdt->base, timeout); 28241b630f4SAnson Huang 28341b630f4SAnson Huang if (watchdog_active(&imx7ulp_wdt->wdd)) 28441b630f4SAnson Huang imx7ulp_wdt_start(&imx7ulp_wdt->wdd); 28541b630f4SAnson Huang 28641b630f4SAnson Huang return 0; 28741b630f4SAnson Huang } 28841b630f4SAnson Huang 28941b630f4SAnson Huang static SIMPLE_DEV_PM_OPS(imx7ulp_wdt_pm_ops, imx7ulp_wdt_suspend, 29041b630f4SAnson Huang imx7ulp_wdt_resume); 29141b630f4SAnson Huang 29241b630f4SAnson Huang static const struct of_device_id imx7ulp_wdt_dt_ids[] = { 29341b630f4SAnson Huang { .compatible = "fsl,imx7ulp-wdt", }, 29441b630f4SAnson Huang { /* sentinel */ } 29541b630f4SAnson Huang }; 29641b630f4SAnson Huang MODULE_DEVICE_TABLE(of, imx7ulp_wdt_dt_ids); 29741b630f4SAnson Huang 29841b630f4SAnson Huang static struct platform_driver imx7ulp_wdt_driver = { 29941b630f4SAnson Huang .probe = imx7ulp_wdt_probe, 30041b630f4SAnson Huang .driver = { 30141b630f4SAnson Huang .name = "imx7ulp-wdt", 30241b630f4SAnson Huang .pm = &imx7ulp_wdt_pm_ops, 30341b630f4SAnson Huang .of_match_table = imx7ulp_wdt_dt_ids, 30441b630f4SAnson Huang }, 30541b630f4SAnson Huang }; 30641b630f4SAnson Huang module_platform_driver(imx7ulp_wdt_driver); 30741b630f4SAnson Huang 30841b630f4SAnson Huang MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>"); 30941b630f4SAnson Huang MODULE_DESCRIPTION("Freescale i.MX7ULP watchdog driver"); 31041b630f4SAnson Huang MODULE_LICENSE("GPL v2"); 311