1258945Sroberto// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 2258945Sroberto/* 3258945Sroberto * Copyright (c) 2016 BayLibre, SAS. 4258945Sroberto * Author: Neil Armstrong <narmstrong@baylibre.com> 5258945Sroberto * 6258945Sroberto */ 7258945Sroberto#include <linux/clk.h> 8258945Sroberto#include <linux/err.h> 9258945Sroberto#include <linux/io.h> 10258945Sroberto#include <linux/module.h> 11258945Sroberto#include <linux/of.h> 12258945Sroberto#include <linux/platform_device.h> 13258945Sroberto#include <linux/slab.h> 14258945Sroberto#include <linux/types.h> 15258945Sroberto#include <linux/watchdog.h> 16258945Sroberto 17258945Sroberto#define DEFAULT_TIMEOUT 30 /* seconds */ 18258945Sroberto 19258945Sroberto#define GXBB_WDT_CTRL_REG 0x0 20258945Sroberto#define GXBB_WDT_TCNT_REG 0x8 21258945Sroberto#define GXBB_WDT_RSET_REG 0xc 22258945Sroberto 23258945Sroberto#define GXBB_WDT_CTRL_CLKDIV_EN BIT(25) 24258945Sroberto#define GXBB_WDT_CTRL_CLK_EN BIT(24) 25258945Sroberto#define GXBB_WDT_CTRL_EN BIT(18) 26258945Sroberto#define GXBB_WDT_CTRL_DIV_MASK (BIT(18) - 1) 27258945Sroberto 28258945Sroberto#define GXBB_WDT_TCNT_SETUP_MASK (BIT(16) - 1) 29258945Sroberto#define GXBB_WDT_TCNT_CNT_SHIFT 16 30258945Sroberto 31258945Srobertostatic bool nowayout = WATCHDOG_NOWAYOUT; 32258945Srobertomodule_param(nowayout, bool, 0); 33258945SrobertoMODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started default=" 34258945Sroberto __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 35258945Sroberto 36258945Srobertostatic unsigned int timeout; 37258945Srobertomodule_param(timeout, uint, 0); 38258945SrobertoMODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds=" 39258945Sroberto __MODULE_STRING(DEFAULT_TIMEOUT) ")"); 40258945Sroberto 41258945Srobertostruct meson_gxbb_wdt { 42258945Sroberto void __iomem *reg_base; 43258945Sroberto struct watchdog_device wdt_dev; 44258945Sroberto struct clk *clk; 45258945Sroberto}; 46258945Sroberto 47258945Srobertostruct wdt_params { 48258945Sroberto u32 rst; 49258945Sroberto}; 50258945Sroberto 51258945Srobertostatic int meson_gxbb_wdt_start(struct watchdog_device *wdt_dev) 52258945Sroberto{ 53258945Sroberto struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev); 54258945Sroberto 55258945Sroberto writel(readl(data->reg_base + GXBB_WDT_CTRL_REG) | GXBB_WDT_CTRL_EN, 56258945Sroberto data->reg_base + GXBB_WDT_CTRL_REG); 57258945Sroberto 58258945Sroberto return 0; 59258945Sroberto} 60258945Sroberto 61258945Srobertostatic int meson_gxbb_wdt_stop(struct watchdog_device *wdt_dev) 62258945Sroberto{ 63258945Sroberto struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev); 64258945Sroberto 65258945Sroberto writel(readl(data->reg_base + GXBB_WDT_CTRL_REG) & ~GXBB_WDT_CTRL_EN, 66258945Sroberto data->reg_base + GXBB_WDT_CTRL_REG); 67258945Sroberto 68258945Sroberto return 0; 69258945Sroberto} 70258945Sroberto 71258945Srobertostatic int meson_gxbb_wdt_ping(struct watchdog_device *wdt_dev) 72258945Sroberto{ 73258945Sroberto struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev); 74258945Sroberto 75258945Sroberto writel(0, data->reg_base + GXBB_WDT_RSET_REG); 76258945Sroberto 77258945Sroberto return 0; 78258945Sroberto} 79258945Sroberto 80258945Srobertostatic int meson_gxbb_wdt_set_timeout(struct watchdog_device *wdt_dev, 81258945Sroberto unsigned int timeout) 82258945Sroberto{ 83258945Sroberto struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev); 84258945Sroberto unsigned long tcnt = timeout * 1000; 85258945Sroberto 86258945Sroberto if (tcnt > GXBB_WDT_TCNT_SETUP_MASK) 87258945Sroberto tcnt = GXBB_WDT_TCNT_SETUP_MASK; 88258945Sroberto 89258945Sroberto wdt_dev->timeout = timeout; 90258945Sroberto 91258945Sroberto meson_gxbb_wdt_ping(wdt_dev); 92258945Sroberto 93258945Sroberto writel(tcnt, data->reg_base + GXBB_WDT_TCNT_REG); 94258945Sroberto 95258945Sroberto return 0; 96258945Sroberto} 97258945Sroberto 98258945Srobertostatic unsigned int meson_gxbb_wdt_get_timeleft(struct watchdog_device *wdt_dev) 99258945Sroberto{ 100258945Sroberto struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev); 101258945Sroberto unsigned long reg; 102258945Sroberto 103258945Sroberto reg = readl(data->reg_base + GXBB_WDT_TCNT_REG); 104258945Sroberto 105258945Sroberto return ((reg & GXBB_WDT_TCNT_SETUP_MASK) - 106258945Sroberto (reg >> GXBB_WDT_TCNT_CNT_SHIFT)) / 1000; 107258945Sroberto} 108258945Sroberto 109258945Srobertostatic const struct watchdog_ops meson_gxbb_wdt_ops = { 110258945Sroberto .start = meson_gxbb_wdt_start, 111258945Sroberto .stop = meson_gxbb_wdt_stop, 112258945Sroberto .ping = meson_gxbb_wdt_ping, 113258945Sroberto .set_timeout = meson_gxbb_wdt_set_timeout, 114258945Sroberto .get_timeleft = meson_gxbb_wdt_get_timeleft, 115258945Sroberto}; 116258945Sroberto 117258945Srobertostatic const struct watchdog_info meson_gxbb_wdt_info = { 118258945Sroberto .identity = "Meson GXBB Watchdog", 119258945Sroberto .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 120258945Sroberto}; 121258945Sroberto 122258945Srobertostatic int __maybe_unused meson_gxbb_wdt_resume(struct device *dev) 123258945Sroberto{ 124258945Sroberto struct meson_gxbb_wdt *data = dev_get_drvdata(dev); 125258945Sroberto 126258945Sroberto if (watchdog_active(&data->wdt_dev)) 127258945Sroberto meson_gxbb_wdt_start(&data->wdt_dev); 128258945Sroberto 129258945Sroberto return 0; 130258945Sroberto} 131258945Sroberto 132258945Srobertostatic int __maybe_unused meson_gxbb_wdt_suspend(struct device *dev) 133258945Sroberto{ 134258945Sroberto struct meson_gxbb_wdt *data = dev_get_drvdata(dev); 135258945Sroberto 136258945Sroberto if (watchdog_active(&data->wdt_dev)) 137258945Sroberto meson_gxbb_wdt_stop(&data->wdt_dev); 138258945Sroberto 139258945Sroberto return 0; 140258945Sroberto} 141258945Sroberto 142258945Srobertostatic const struct dev_pm_ops meson_gxbb_wdt_pm_ops = { 143258945Sroberto SET_SYSTEM_SLEEP_PM_OPS(meson_gxbb_wdt_suspend, meson_gxbb_wdt_resume) 144258945Sroberto}; 145258945Sroberto 146258945Srobertostatic const struct wdt_params gxbb_params = { 147258945Sroberto .rst = BIT(21), 148258945Sroberto}; 149258945Sroberto 150258945Srobertostatic const struct wdt_params t7_params = { 151258945Sroberto .rst = BIT(22), 152258945Sroberto}; 153258945Sroberto 154258945Srobertostatic const struct of_device_id meson_gxbb_wdt_dt_ids[] = { 155258945Sroberto { .compatible = "amlogic,meson-gxbb-wdt", .data = &gxbb_params, }, 156258945Sroberto { .compatible = "amlogic,t7-wdt", .data = &t7_params, }, 157258945Sroberto { /* sentinel */ }, 158258945Sroberto}; 159258945SrobertoMODULE_DEVICE_TABLE(of, meson_gxbb_wdt_dt_ids); 160258945Sroberto 161258945Srobertostatic int meson_gxbb_wdt_probe(struct platform_device *pdev) 162258945Sroberto{ 163258945Sroberto struct device *dev = &pdev->dev; 164258945Sroberto struct meson_gxbb_wdt *data; 165258945Sroberto struct wdt_params *params; 166258945Sroberto u32 ctrl_reg; 167258945Sroberto 168258945Sroberto data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); 169258945Sroberto if (!data) 170258945Sroberto return -ENOMEM; 171258945Sroberto 172258945Sroberto data->reg_base = devm_platform_ioremap_resource(pdev, 0); 173258945Sroberto if (IS_ERR(data->reg_base)) 174258945Sroberto return PTR_ERR(data->reg_base); 175258945Sroberto 176258945Sroberto data->clk = devm_clk_get_enabled(dev, NULL); 177258945Sroberto if (IS_ERR(data->clk)) 178258945Sroberto return PTR_ERR(data->clk); 179258945Sroberto 180258945Sroberto params = (struct wdt_params *)of_device_get_match_data(dev); 181258945Sroberto 182258945Sroberto platform_set_drvdata(pdev, data); 183258945Sroberto 184258945Sroberto data->wdt_dev.parent = dev; 185258945Sroberto data->wdt_dev.info = &meson_gxbb_wdt_info; 186258945Sroberto data->wdt_dev.ops = &meson_gxbb_wdt_ops; 187258945Sroberto data->wdt_dev.max_hw_heartbeat_ms = GXBB_WDT_TCNT_SETUP_MASK; 188258945Sroberto data->wdt_dev.min_timeout = 1; 189258945Sroberto data->wdt_dev.timeout = DEFAULT_TIMEOUT; 190258945Sroberto watchdog_init_timeout(&data->wdt_dev, timeout, dev); 191258945Sroberto watchdog_set_nowayout(&data->wdt_dev, nowayout); 192258945Sroberto watchdog_set_drvdata(&data->wdt_dev, data); 193258945Sroberto 194258945Sroberto ctrl_reg = readl(data->reg_base + GXBB_WDT_CTRL_REG) & 195258945Sroberto GXBB_WDT_CTRL_EN; 196258945Sroberto 197258945Sroberto if (ctrl_reg) { 198258945Sroberto /* Watchdog is running - keep it running but extend timeout 199258945Sroberto * to the maximum while setting the timebase 200258945Sroberto */ 201258945Sroberto set_bit(WDOG_HW_RUNNING, &data->wdt_dev.status); 202258945Sroberto meson_gxbb_wdt_set_timeout(&data->wdt_dev, 203258945Sroberto GXBB_WDT_TCNT_SETUP_MASK / 1000); 204258945Sroberto } 205258945Sroberto 206258945Sroberto /* Setup with 1ms timebase */ 207258945Sroberto ctrl_reg |= ((clk_get_rate(data->clk) / 1000) & 208258945Sroberto GXBB_WDT_CTRL_DIV_MASK) | 209258945Sroberto params->rst | 210258945Sroberto GXBB_WDT_CTRL_CLK_EN | 211258945Sroberto GXBB_WDT_CTRL_CLKDIV_EN; 212258945Sroberto 213258945Sroberto writel(ctrl_reg, data->reg_base + GXBB_WDT_CTRL_REG); 214258945Sroberto meson_gxbb_wdt_set_timeout(&data->wdt_dev, data->wdt_dev.timeout); 215258945Sroberto 216258945Sroberto return devm_watchdog_register_device(dev, &data->wdt_dev); 217258945Sroberto} 218258945Sroberto 219258945Srobertostatic struct platform_driver meson_gxbb_wdt_driver = { 220258945Sroberto .probe = meson_gxbb_wdt_probe, 221258945Sroberto .driver = { 222258945Sroberto .name = "meson-gxbb-wdt", 223258945Sroberto .pm = &meson_gxbb_wdt_pm_ops, 224258945Sroberto .of_match_table = meson_gxbb_wdt_dt_ids, 225258945Sroberto }, 226258945Sroberto}; 227258945Sroberto 228258945Srobertomodule_platform_driver(meson_gxbb_wdt_driver); 229258945Sroberto 230258945SrobertoMODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); 231258945SrobertoMODULE_DESCRIPTION("Amlogic Meson GXBB Watchdog timer driver"); 232258945SrobertoMODULE_LICENSE("Dual BSD/GPL"); 233258945Sroberto