12e62c498SMarcus Folkesson // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 2683fa50fSNeil Armstrong /* 3683fa50fSNeil Armstrong * Copyright (c) 2016 BayLibre, SAS. 4683fa50fSNeil Armstrong * Author: Neil Armstrong <narmstrong@baylibre.com> 5683fa50fSNeil Armstrong * 6683fa50fSNeil Armstrong */ 7683fa50fSNeil Armstrong #include <linux/clk.h> 8683fa50fSNeil Armstrong #include <linux/err.h> 9683fa50fSNeil Armstrong #include <linux/io.h> 10683fa50fSNeil Armstrong #include <linux/module.h> 11683fa50fSNeil Armstrong #include <linux/of.h> 12683fa50fSNeil Armstrong #include <linux/platform_device.h> 13683fa50fSNeil Armstrong #include <linux/slab.h> 14683fa50fSNeil Armstrong #include <linux/types.h> 15683fa50fSNeil Armstrong #include <linux/watchdog.h> 16683fa50fSNeil Armstrong 17683fa50fSNeil Armstrong #define DEFAULT_TIMEOUT 30 /* seconds */ 18683fa50fSNeil Armstrong 19683fa50fSNeil Armstrong #define GXBB_WDT_CTRL_REG 0x0 20683fa50fSNeil Armstrong #define GXBB_WDT_TCNT_REG 0x8 21683fa50fSNeil Armstrong #define GXBB_WDT_RSET_REG 0xc 22683fa50fSNeil Armstrong 23683fa50fSNeil Armstrong #define GXBB_WDT_CTRL_CLKDIV_EN BIT(25) 24683fa50fSNeil Armstrong #define GXBB_WDT_CTRL_CLK_EN BIT(24) 25683fa50fSNeil Armstrong #define GXBB_WDT_CTRL_EE_RESET BIT(21) 26683fa50fSNeil Armstrong #define GXBB_WDT_CTRL_EN BIT(18) 27683fa50fSNeil Armstrong #define GXBB_WDT_CTRL_DIV_MASK (BIT(18) - 1) 28683fa50fSNeil Armstrong 29683fa50fSNeil Armstrong #define GXBB_WDT_TCNT_SETUP_MASK (BIT(16) - 1) 30683fa50fSNeil Armstrong #define GXBB_WDT_TCNT_CNT_SHIFT 16 31683fa50fSNeil Armstrong 32683fa50fSNeil Armstrong struct meson_gxbb_wdt { 33683fa50fSNeil Armstrong void __iomem *reg_base; 34683fa50fSNeil Armstrong struct watchdog_device wdt_dev; 35683fa50fSNeil Armstrong struct clk *clk; 36683fa50fSNeil Armstrong }; 37683fa50fSNeil Armstrong 38683fa50fSNeil Armstrong static int meson_gxbb_wdt_start(struct watchdog_device *wdt_dev) 39683fa50fSNeil Armstrong { 40683fa50fSNeil Armstrong struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev); 41683fa50fSNeil Armstrong 42683fa50fSNeil Armstrong writel(readl(data->reg_base + GXBB_WDT_CTRL_REG) | GXBB_WDT_CTRL_EN, 43683fa50fSNeil Armstrong data->reg_base + GXBB_WDT_CTRL_REG); 44683fa50fSNeil Armstrong 45683fa50fSNeil Armstrong return 0; 46683fa50fSNeil Armstrong } 47683fa50fSNeil Armstrong 48683fa50fSNeil Armstrong static int meson_gxbb_wdt_stop(struct watchdog_device *wdt_dev) 49683fa50fSNeil Armstrong { 50683fa50fSNeil Armstrong struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev); 51683fa50fSNeil Armstrong 52683fa50fSNeil Armstrong writel(readl(data->reg_base + GXBB_WDT_CTRL_REG) & ~GXBB_WDT_CTRL_EN, 53683fa50fSNeil Armstrong data->reg_base + GXBB_WDT_CTRL_REG); 54683fa50fSNeil Armstrong 55683fa50fSNeil Armstrong return 0; 56683fa50fSNeil Armstrong } 57683fa50fSNeil Armstrong 58683fa50fSNeil Armstrong static int meson_gxbb_wdt_ping(struct watchdog_device *wdt_dev) 59683fa50fSNeil Armstrong { 60683fa50fSNeil Armstrong struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev); 61683fa50fSNeil Armstrong 62683fa50fSNeil Armstrong writel(0, data->reg_base + GXBB_WDT_RSET_REG); 63683fa50fSNeil Armstrong 64683fa50fSNeil Armstrong return 0; 65683fa50fSNeil Armstrong } 66683fa50fSNeil Armstrong 67683fa50fSNeil Armstrong static int meson_gxbb_wdt_set_timeout(struct watchdog_device *wdt_dev, 68683fa50fSNeil Armstrong unsigned int timeout) 69683fa50fSNeil Armstrong { 70683fa50fSNeil Armstrong struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev); 71683fa50fSNeil Armstrong unsigned long tcnt = timeout * 1000; 72683fa50fSNeil Armstrong 73683fa50fSNeil Armstrong if (tcnt > GXBB_WDT_TCNT_SETUP_MASK) 74683fa50fSNeil Armstrong tcnt = GXBB_WDT_TCNT_SETUP_MASK; 75683fa50fSNeil Armstrong 76683fa50fSNeil Armstrong wdt_dev->timeout = timeout; 77683fa50fSNeil Armstrong 78683fa50fSNeil Armstrong meson_gxbb_wdt_ping(wdt_dev); 79683fa50fSNeil Armstrong 80683fa50fSNeil Armstrong writel(tcnt, data->reg_base + GXBB_WDT_TCNT_REG); 81683fa50fSNeil Armstrong 82683fa50fSNeil Armstrong return 0; 83683fa50fSNeil Armstrong } 84683fa50fSNeil Armstrong 85683fa50fSNeil Armstrong static unsigned int meson_gxbb_wdt_get_timeleft(struct watchdog_device *wdt_dev) 86683fa50fSNeil Armstrong { 87683fa50fSNeil Armstrong struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev); 88683fa50fSNeil Armstrong unsigned long reg; 89683fa50fSNeil Armstrong 90683fa50fSNeil Armstrong reg = readl(data->reg_base + GXBB_WDT_TCNT_REG); 91683fa50fSNeil Armstrong 92*2c777346SXingyu Chen return ((reg & GXBB_WDT_TCNT_SETUP_MASK) - 93*2c777346SXingyu Chen (reg >> GXBB_WDT_TCNT_CNT_SHIFT)) / 1000; 94683fa50fSNeil Armstrong } 95683fa50fSNeil Armstrong 96683fa50fSNeil Armstrong static const struct watchdog_ops meson_gxbb_wdt_ops = { 97683fa50fSNeil Armstrong .start = meson_gxbb_wdt_start, 98683fa50fSNeil Armstrong .stop = meson_gxbb_wdt_stop, 99683fa50fSNeil Armstrong .ping = meson_gxbb_wdt_ping, 100683fa50fSNeil Armstrong .set_timeout = meson_gxbb_wdt_set_timeout, 101683fa50fSNeil Armstrong .get_timeleft = meson_gxbb_wdt_get_timeleft, 102683fa50fSNeil Armstrong }; 103683fa50fSNeil Armstrong 104683fa50fSNeil Armstrong static const struct watchdog_info meson_gxbb_wdt_info = { 105683fa50fSNeil Armstrong .identity = "Meson GXBB Watchdog", 106683fa50fSNeil Armstrong .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 107683fa50fSNeil Armstrong }; 108683fa50fSNeil Armstrong 109683fa50fSNeil Armstrong static int __maybe_unused meson_gxbb_wdt_resume(struct device *dev) 110683fa50fSNeil Armstrong { 111683fa50fSNeil Armstrong struct meson_gxbb_wdt *data = dev_get_drvdata(dev); 112683fa50fSNeil Armstrong 113683fa50fSNeil Armstrong if (watchdog_active(&data->wdt_dev)) 114683fa50fSNeil Armstrong meson_gxbb_wdt_start(&data->wdt_dev); 115683fa50fSNeil Armstrong 116683fa50fSNeil Armstrong return 0; 117683fa50fSNeil Armstrong } 118683fa50fSNeil Armstrong 119683fa50fSNeil Armstrong static int __maybe_unused meson_gxbb_wdt_suspend(struct device *dev) 120683fa50fSNeil Armstrong { 121683fa50fSNeil Armstrong struct meson_gxbb_wdt *data = dev_get_drvdata(dev); 122683fa50fSNeil Armstrong 123683fa50fSNeil Armstrong if (watchdog_active(&data->wdt_dev)) 124683fa50fSNeil Armstrong meson_gxbb_wdt_stop(&data->wdt_dev); 125683fa50fSNeil Armstrong 126683fa50fSNeil Armstrong return 0; 127683fa50fSNeil Armstrong } 128683fa50fSNeil Armstrong 129683fa50fSNeil Armstrong static const struct dev_pm_ops meson_gxbb_wdt_pm_ops = { 130683fa50fSNeil Armstrong SET_SYSTEM_SLEEP_PM_OPS(meson_gxbb_wdt_suspend, meson_gxbb_wdt_resume) 131683fa50fSNeil Armstrong }; 132683fa50fSNeil Armstrong 133683fa50fSNeil Armstrong static const struct of_device_id meson_gxbb_wdt_dt_ids[] = { 134683fa50fSNeil Armstrong { .compatible = "amlogic,meson-gxbb-wdt", }, 135683fa50fSNeil Armstrong { /* sentinel */ }, 136683fa50fSNeil Armstrong }; 137683fa50fSNeil Armstrong MODULE_DEVICE_TABLE(of, meson_gxbb_wdt_dt_ids); 138683fa50fSNeil Armstrong 1391678f830SGuenter Roeck static void meson_clk_disable_unprepare(void *data) 1401678f830SGuenter Roeck { 1411678f830SGuenter Roeck clk_disable_unprepare(data); 1421678f830SGuenter Roeck } 1431678f830SGuenter Roeck 144683fa50fSNeil Armstrong static int meson_gxbb_wdt_probe(struct platform_device *pdev) 145683fa50fSNeil Armstrong { 1461678f830SGuenter Roeck struct device *dev = &pdev->dev; 147683fa50fSNeil Armstrong struct meson_gxbb_wdt *data; 148683fa50fSNeil Armstrong int ret; 149683fa50fSNeil Armstrong 1501678f830SGuenter Roeck data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); 151683fa50fSNeil Armstrong if (!data) 152683fa50fSNeil Armstrong return -ENOMEM; 153683fa50fSNeil Armstrong 1540f0a6a28SGuenter Roeck data->reg_base = devm_platform_ioremap_resource(pdev, 0); 155683fa50fSNeil Armstrong if (IS_ERR(data->reg_base)) 156683fa50fSNeil Armstrong return PTR_ERR(data->reg_base); 157683fa50fSNeil Armstrong 1581678f830SGuenter Roeck data->clk = devm_clk_get(dev, NULL); 159683fa50fSNeil Armstrong if (IS_ERR(data->clk)) 160683fa50fSNeil Armstrong return PTR_ERR(data->clk); 161683fa50fSNeil Armstrong 16265360944SArvind Yadav ret = clk_prepare_enable(data->clk); 16365360944SArvind Yadav if (ret) 16465360944SArvind Yadav return ret; 1651678f830SGuenter Roeck ret = devm_add_action_or_reset(dev, meson_clk_disable_unprepare, 1661678f830SGuenter Roeck data->clk); 1671678f830SGuenter Roeck if (ret) 1681678f830SGuenter Roeck return ret; 169683fa50fSNeil Armstrong 170683fa50fSNeil Armstrong platform_set_drvdata(pdev, data); 171683fa50fSNeil Armstrong 1721678f830SGuenter Roeck data->wdt_dev.parent = dev; 173683fa50fSNeil Armstrong data->wdt_dev.info = &meson_gxbb_wdt_info; 174683fa50fSNeil Armstrong data->wdt_dev.ops = &meson_gxbb_wdt_ops; 175683fa50fSNeil Armstrong data->wdt_dev.max_hw_heartbeat_ms = GXBB_WDT_TCNT_SETUP_MASK; 176683fa50fSNeil Armstrong data->wdt_dev.min_timeout = 1; 177683fa50fSNeil Armstrong data->wdt_dev.timeout = DEFAULT_TIMEOUT; 178683fa50fSNeil Armstrong watchdog_set_drvdata(&data->wdt_dev, data); 179683fa50fSNeil Armstrong 180683fa50fSNeil Armstrong /* Setup with 1ms timebase */ 181683fa50fSNeil Armstrong writel(((clk_get_rate(data->clk) / 1000) & GXBB_WDT_CTRL_DIV_MASK) | 182683fa50fSNeil Armstrong GXBB_WDT_CTRL_EE_RESET | 183683fa50fSNeil Armstrong GXBB_WDT_CTRL_CLK_EN | 184683fa50fSNeil Armstrong GXBB_WDT_CTRL_CLKDIV_EN, 185683fa50fSNeil Armstrong data->reg_base + GXBB_WDT_CTRL_REG); 186683fa50fSNeil Armstrong 187683fa50fSNeil Armstrong meson_gxbb_wdt_set_timeout(&data->wdt_dev, data->wdt_dev.timeout); 188683fa50fSNeil Armstrong 1891678f830SGuenter Roeck watchdog_stop_on_reboot(&data->wdt_dev); 1901678f830SGuenter Roeck return devm_watchdog_register_device(dev, &data->wdt_dev); 191683fa50fSNeil Armstrong } 192683fa50fSNeil Armstrong 193683fa50fSNeil Armstrong static struct platform_driver meson_gxbb_wdt_driver = { 194683fa50fSNeil Armstrong .probe = meson_gxbb_wdt_probe, 195683fa50fSNeil Armstrong .driver = { 196683fa50fSNeil Armstrong .name = "meson-gxbb-wdt", 197683fa50fSNeil Armstrong .pm = &meson_gxbb_wdt_pm_ops, 198683fa50fSNeil Armstrong .of_match_table = meson_gxbb_wdt_dt_ids, 199683fa50fSNeil Armstrong }, 200683fa50fSNeil Armstrong }; 201683fa50fSNeil Armstrong 202683fa50fSNeil Armstrong module_platform_driver(meson_gxbb_wdt_driver); 203683fa50fSNeil Armstrong 204683fa50fSNeil Armstrong MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); 205683fa50fSNeil Armstrong MODULE_DESCRIPTION("Amlogic Meson GXBB Watchdog timer driver"); 206683fa50fSNeil Armstrong MODULE_LICENSE("Dual BSD/GPL"); 207