1a912e80bSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 2f865c352SPaul Cercueil /* 3f865c352SPaul Cercueil * Copyright (C) 2010, Paul Cercueil <paul@crapouillou.net> 4f865c352SPaul Cercueil * JZ4740 Watchdog driver 5f865c352SPaul Cercueil */ 6f865c352SPaul Cercueil 7df04cce3SPaul Cercueil #include <linux/mfd/ingenic-tcu.h> 8f865c352SPaul Cercueil #include <linux/module.h> 9f865c352SPaul Cercueil #include <linux/moduleparam.h> 10f865c352SPaul Cercueil #include <linux/types.h> 11f865c352SPaul Cercueil #include <linux/kernel.h> 12f865c352SPaul Cercueil #include <linux/watchdog.h> 13f865c352SPaul Cercueil #include <linux/platform_device.h> 14f865c352SPaul Cercueil #include <linux/io.h> 15f865c352SPaul Cercueil #include <linux/device.h> 16f865c352SPaul Cercueil #include <linux/clk.h> 17f865c352SPaul Cercueil #include <linux/slab.h> 1885f6df14SAxel Lin #include <linux/err.h> 196b96c722SZubair Lutfullah Kakakhel #include <linux/of.h> 20f865c352SPaul Cercueil 21f865c352SPaul Cercueil #define DEFAULT_HEARTBEAT 5 22f865c352SPaul Cercueil #define MAX_HEARTBEAT 2048 23f865c352SPaul Cercueil 2485f6df14SAxel Lin static bool nowayout = WATCHDOG_NOWAYOUT; 2585f6df14SAxel Lin module_param(nowayout, bool, 0); 2685f6df14SAxel Lin MODULE_PARM_DESC(nowayout, 2785f6df14SAxel Lin "Watchdog cannot be stopped once started (default=" 2885f6df14SAxel Lin __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 2985f6df14SAxel Lin 3085f6df14SAxel Lin static unsigned int heartbeat = DEFAULT_HEARTBEAT; 3185f6df14SAxel Lin module_param(heartbeat, uint, 0); 3285f6df14SAxel Lin MODULE_PARM_DESC(heartbeat, 3385f6df14SAxel Lin "Watchdog heartbeat period in seconds from 1 to " 3485f6df14SAxel Lin __MODULE_STRING(MAX_HEARTBEAT) ", default " 3585f6df14SAxel Lin __MODULE_STRING(DEFAULT_HEARTBEAT)); 3685f6df14SAxel Lin 3785f6df14SAxel Lin struct jz4740_wdt_drvdata { 3885f6df14SAxel Lin struct watchdog_device wdt; 39f865c352SPaul Cercueil void __iomem *base; 40*1d9c3074SPaul Cercueil struct clk *clk; 41*1d9c3074SPaul Cercueil unsigned long clk_rate; 4285f6df14SAxel Lin }; 43f865c352SPaul Cercueil 4485f6df14SAxel Lin static int jz4740_wdt_ping(struct watchdog_device *wdt_dev) 45f865c352SPaul Cercueil { 4685f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 4785f6df14SAxel Lin 48df04cce3SPaul Cercueil writew(0x0, drvdata->base + TCU_REG_WDT_TCNT); 4985f6df14SAxel Lin return 0; 50f865c352SPaul Cercueil } 51f865c352SPaul Cercueil 5285f6df14SAxel Lin static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev, 5385f6df14SAxel Lin unsigned int new_timeout) 54f865c352SPaul Cercueil { 5585f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 56*1d9c3074SPaul Cercueil u16 timeout_value = (u16)(drvdata->clk_rate * new_timeout); 579b346118SPaul Cercueil u8 tcer; 58f865c352SPaul Cercueil 599b346118SPaul Cercueil tcer = readb(drvdata->base + TCU_REG_WDT_TCER); 60df04cce3SPaul Cercueil writeb(0x0, drvdata->base + TCU_REG_WDT_TCER); 61f865c352SPaul Cercueil 62df04cce3SPaul Cercueil writew((u16)timeout_value, drvdata->base + TCU_REG_WDT_TDR); 63df04cce3SPaul Cercueil writew(0x0, drvdata->base + TCU_REG_WDT_TCNT); 64f865c352SPaul Cercueil 659b346118SPaul Cercueil if (tcer & TCU_WDT_TCER_TCEN) 669b346118SPaul Cercueil writeb(TCU_WDT_TCER_TCEN, drvdata->base + TCU_REG_WDT_TCER); 6785f6df14SAxel Lin 680197c1c4SWim Van Sebroeck wdt_dev->timeout = new_timeout; 6985f6df14SAxel Lin return 0; 70f865c352SPaul Cercueil } 71f865c352SPaul Cercueil 7285f6df14SAxel Lin static int jz4740_wdt_start(struct watchdog_device *wdt_dev) 73f865c352SPaul Cercueil { 749b346118SPaul Cercueil struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 75*1d9c3074SPaul Cercueil int ret; 769b346118SPaul Cercueil u8 tcer; 779b346118SPaul Cercueil 78*1d9c3074SPaul Cercueil ret = clk_prepare_enable(drvdata->clk); 79*1d9c3074SPaul Cercueil if (ret) 80*1d9c3074SPaul Cercueil return ret; 81*1d9c3074SPaul Cercueil 829b346118SPaul Cercueil tcer = readb(drvdata->base + TCU_REG_WDT_TCER); 839b346118SPaul Cercueil 8485f6df14SAxel Lin jz4740_wdt_set_timeout(wdt_dev, wdt_dev->timeout); 8585f6df14SAxel Lin 869b346118SPaul Cercueil /* Start watchdog if it wasn't started already */ 879b346118SPaul Cercueil if (!(tcer & TCU_WDT_TCER_TCEN)) 889b346118SPaul Cercueil writeb(TCU_WDT_TCER_TCEN, drvdata->base + TCU_REG_WDT_TCER); 899b346118SPaul Cercueil 9085f6df14SAxel Lin return 0; 91f865c352SPaul Cercueil } 92f865c352SPaul Cercueil 9385f6df14SAxel Lin static int jz4740_wdt_stop(struct watchdog_device *wdt_dev) 94f865c352SPaul Cercueil { 9585f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 9685f6df14SAxel Lin 97df04cce3SPaul Cercueil writeb(0x0, drvdata->base + TCU_REG_WDT_TCER); 98*1d9c3074SPaul Cercueil clk_disable_unprepare(drvdata->clk); 9985f6df14SAxel Lin 10085f6df14SAxel Lin return 0; 101f865c352SPaul Cercueil } 102f865c352SPaul Cercueil 103b4918057SPaul Cercueil static int jz4740_wdt_restart(struct watchdog_device *wdt_dev, 104b4918057SPaul Cercueil unsigned long action, void *data) 105b4918057SPaul Cercueil { 106b4918057SPaul Cercueil wdt_dev->timeout = 0; 107b4918057SPaul Cercueil jz4740_wdt_start(wdt_dev); 108b4918057SPaul Cercueil return 0; 109b4918057SPaul Cercueil } 110b4918057SPaul Cercueil 11185f6df14SAxel Lin static const struct watchdog_info jz4740_wdt_info = { 11285f6df14SAxel Lin .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 113f865c352SPaul Cercueil .identity = "jz4740 Watchdog", 114f865c352SPaul Cercueil }; 115f865c352SPaul Cercueil 11685f6df14SAxel Lin static const struct watchdog_ops jz4740_wdt_ops = { 117f865c352SPaul Cercueil .owner = THIS_MODULE, 11885f6df14SAxel Lin .start = jz4740_wdt_start, 11985f6df14SAxel Lin .stop = jz4740_wdt_stop, 12085f6df14SAxel Lin .ping = jz4740_wdt_ping, 12185f6df14SAxel Lin .set_timeout = jz4740_wdt_set_timeout, 122b4918057SPaul Cercueil .restart = jz4740_wdt_restart, 123f865c352SPaul Cercueil }; 124f865c352SPaul Cercueil 1256b96c722SZubair Lutfullah Kakakhel #ifdef CONFIG_OF 1266b96c722SZubair Lutfullah Kakakhel static const struct of_device_id jz4740_wdt_of_matches[] = { 1276b96c722SZubair Lutfullah Kakakhel { .compatible = "ingenic,jz4740-watchdog", }, 12871246c35SMathieu Malaterre { .compatible = "ingenic,jz4780-watchdog", }, 1296b96c722SZubair Lutfullah Kakakhel { /* sentinel */ } 1306b96c722SZubair Lutfullah Kakakhel }; 13135ffa961SStephen Boyd MODULE_DEVICE_TABLE(of, jz4740_wdt_of_matches); 1326b96c722SZubair Lutfullah Kakakhel #endif 1336b96c722SZubair Lutfullah Kakakhel 1342d991a16SBill Pemberton static int jz4740_wdt_probe(struct platform_device *pdev) 135f865c352SPaul Cercueil { 13602189bb9SGuenter Roeck struct device *dev = &pdev->dev; 13785f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata; 13885f6df14SAxel Lin struct watchdog_device *jz4740_wdt; 139*1d9c3074SPaul Cercueil long rate; 140*1d9c3074SPaul Cercueil int ret; 14185f6df14SAxel Lin 14202189bb9SGuenter Roeck drvdata = devm_kzalloc(dev, sizeof(struct jz4740_wdt_drvdata), 14385f6df14SAxel Lin GFP_KERNEL); 144e26e74b1SColin Ian King if (!drvdata) 14585f6df14SAxel Lin return -ENOMEM; 14685f6df14SAxel Lin 147*1d9c3074SPaul Cercueil drvdata->clk = devm_clk_get(&pdev->dev, "wdt"); 148*1d9c3074SPaul Cercueil if (IS_ERR(drvdata->clk)) { 149*1d9c3074SPaul Cercueil dev_err(&pdev->dev, "cannot find WDT clock\n"); 150*1d9c3074SPaul Cercueil return PTR_ERR(drvdata->clk); 151*1d9c3074SPaul Cercueil } 15285f6df14SAxel Lin 153*1d9c3074SPaul Cercueil /* Set smallest clock possible */ 154*1d9c3074SPaul Cercueil rate = clk_round_rate(drvdata->clk, 1); 155*1d9c3074SPaul Cercueil if (rate < 0) 156*1d9c3074SPaul Cercueil return rate; 157*1d9c3074SPaul Cercueil 158*1d9c3074SPaul Cercueil ret = clk_set_rate(drvdata->clk, rate); 159*1d9c3074SPaul Cercueil if (ret) 160*1d9c3074SPaul Cercueil return ret; 161*1d9c3074SPaul Cercueil 162*1d9c3074SPaul Cercueil drvdata->clk_rate = rate; 16385f6df14SAxel Lin jz4740_wdt = &drvdata->wdt; 16485f6df14SAxel Lin jz4740_wdt->info = &jz4740_wdt_info; 16585f6df14SAxel Lin jz4740_wdt->ops = &jz4740_wdt_ops; 16685f6df14SAxel Lin jz4740_wdt->min_timeout = 1; 167*1d9c3074SPaul Cercueil jz4740_wdt->max_timeout = 0xffff / rate; 168*1d9c3074SPaul Cercueil jz4740_wdt->timeout = clamp(heartbeat, 169*1d9c3074SPaul Cercueil jz4740_wdt->min_timeout, 170*1d9c3074SPaul Cercueil jz4740_wdt->max_timeout); 17102189bb9SGuenter Roeck jz4740_wdt->parent = dev; 17285f6df14SAxel Lin watchdog_set_nowayout(jz4740_wdt, nowayout); 17385f6df14SAxel Lin watchdog_set_drvdata(jz4740_wdt, drvdata); 174f865c352SPaul Cercueil 1750f0a6a28SGuenter Roeck drvdata->base = devm_platform_ioremap_resource(pdev, 0); 1766bdbc1f7SPaul Cercueil if (IS_ERR(drvdata->base)) 1776bdbc1f7SPaul Cercueil return PTR_ERR(drvdata->base); 178f865c352SPaul Cercueil 1799ee644c9SWolfram Sang return devm_watchdog_register_device(dev, &drvdata->wdt); 180f865c352SPaul Cercueil } 181f865c352SPaul Cercueil 182f865c352SPaul Cercueil static struct platform_driver jz4740_wdt_driver = { 183f865c352SPaul Cercueil .probe = jz4740_wdt_probe, 184f865c352SPaul Cercueil .driver = { 185f865c352SPaul Cercueil .name = "jz4740-wdt", 1866b96c722SZubair Lutfullah Kakakhel .of_match_table = of_match_ptr(jz4740_wdt_of_matches), 187f865c352SPaul Cercueil }, 188f865c352SPaul Cercueil }; 189f865c352SPaul Cercueil 190b8ec6118SAxel Lin module_platform_driver(jz4740_wdt_driver); 191f865c352SPaul Cercueil 192f865c352SPaul Cercueil MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); 193f865c352SPaul Cercueil MODULE_DESCRIPTION("jz4740 Watchdog Driver"); 194f865c352SPaul Cercueil MODULE_LICENSE("GPL"); 195f865c352SPaul Cercueil MODULE_ALIAS("platform:jz4740-wdt"); 196