12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 222e1b8f6SCarlo Caione /* 322e1b8f6SCarlo Caione * Meson Watchdog Driver 422e1b8f6SCarlo Caione * 522e1b8f6SCarlo Caione * Copyright (c) 2014 Carlo Caione 622e1b8f6SCarlo Caione */ 722e1b8f6SCarlo Caione 822e1b8f6SCarlo Caione #include <linux/clk.h> 922e1b8f6SCarlo Caione #include <linux/delay.h> 1022e1b8f6SCarlo Caione #include <linux/err.h> 1122e1b8f6SCarlo Caione #include <linux/init.h> 1222e1b8f6SCarlo Caione #include <linux/io.h> 1322e1b8f6SCarlo Caione #include <linux/kernel.h> 1422e1b8f6SCarlo Caione #include <linux/module.h> 1522e1b8f6SCarlo Caione #include <linux/moduleparam.h> 1622e1b8f6SCarlo Caione #include <linux/of.h> 17943bf1f6SCarlo Caione #include <linux/of_device.h> 1822e1b8f6SCarlo Caione #include <linux/platform_device.h> 1922e1b8f6SCarlo Caione #include <linux/types.h> 2022e1b8f6SCarlo Caione #include <linux/watchdog.h> 2122e1b8f6SCarlo Caione 2222e1b8f6SCarlo Caione #define DRV_NAME "meson_wdt" 2322e1b8f6SCarlo Caione 2422e1b8f6SCarlo Caione #define MESON_WDT_TC 0x00 2522e1b8f6SCarlo Caione #define MESON_WDT_DC_RESET (3 << 24) 2622e1b8f6SCarlo Caione 2722e1b8f6SCarlo Caione #define MESON_WDT_RESET 0x04 2822e1b8f6SCarlo Caione 2922e1b8f6SCarlo Caione #define MESON_WDT_TIMEOUT 30 3022e1b8f6SCarlo Caione #define MESON_WDT_MIN_TIMEOUT 1 3122e1b8f6SCarlo Caione 32943bf1f6SCarlo Caione #define MESON_SEC_TO_TC(s, c) ((s) * (c)) 3322e1b8f6SCarlo Caione 3422e1b8f6SCarlo Caione static bool nowayout = WATCHDOG_NOWAYOUT; 354590d62cSMarcus Folkesson static unsigned int timeout; 3622e1b8f6SCarlo Caione 37943bf1f6SCarlo Caione struct meson_wdt_data { 38943bf1f6SCarlo Caione unsigned int enable; 39943bf1f6SCarlo Caione unsigned int terminal_count_mask; 40943bf1f6SCarlo Caione unsigned int count_unit; 41943bf1f6SCarlo Caione }; 42943bf1f6SCarlo Caione 43943bf1f6SCarlo Caione static struct meson_wdt_data meson6_wdt_data = { 44943bf1f6SCarlo Caione .enable = BIT(22), 45943bf1f6SCarlo Caione .terminal_count_mask = 0x3fffff, 46943bf1f6SCarlo Caione .count_unit = 100000, /* 10 us */ 47943bf1f6SCarlo Caione }; 48943bf1f6SCarlo Caione 4971388840SCarlo Caione static struct meson_wdt_data meson8b_wdt_data = { 5071388840SCarlo Caione .enable = BIT(19), 5171388840SCarlo Caione .terminal_count_mask = 0xffff, 5271388840SCarlo Caione .count_unit = 7812, /* 128 us */ 5371388840SCarlo Caione }; 5471388840SCarlo Caione 5522e1b8f6SCarlo Caione struct meson_wdt_dev { 5622e1b8f6SCarlo Caione struct watchdog_device wdt_dev; 5722e1b8f6SCarlo Caione void __iomem *wdt_base; 58943bf1f6SCarlo Caione const struct meson_wdt_data *data; 5922e1b8f6SCarlo Caione }; 6022e1b8f6SCarlo Caione 614d8b229dSGuenter Roeck static int meson_wdt_restart(struct watchdog_device *wdt_dev, 624d8b229dSGuenter Roeck unsigned long action, void *data) 6322e1b8f6SCarlo Caione { 641b6fd59aSDamien Riegel struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); 65943bf1f6SCarlo Caione u32 tc_reboot = MESON_WDT_DC_RESET; 66943bf1f6SCarlo Caione 67943bf1f6SCarlo Caione tc_reboot |= meson_wdt->data->enable; 6822e1b8f6SCarlo Caione 6922e1b8f6SCarlo Caione while (1) { 7022e1b8f6SCarlo Caione writel(tc_reboot, meson_wdt->wdt_base + MESON_WDT_TC); 7122e1b8f6SCarlo Caione mdelay(5); 7222e1b8f6SCarlo Caione } 7322e1b8f6SCarlo Caione 741b6fd59aSDamien Riegel return 0; 7522e1b8f6SCarlo Caione } 7622e1b8f6SCarlo Caione 7722e1b8f6SCarlo Caione static int meson_wdt_ping(struct watchdog_device *wdt_dev) 7822e1b8f6SCarlo Caione { 7922e1b8f6SCarlo Caione struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); 8022e1b8f6SCarlo Caione 8122e1b8f6SCarlo Caione writel(0, meson_wdt->wdt_base + MESON_WDT_RESET); 8222e1b8f6SCarlo Caione 8322e1b8f6SCarlo Caione return 0; 8422e1b8f6SCarlo Caione } 8522e1b8f6SCarlo Caione 8622e1b8f6SCarlo Caione static void meson_wdt_change_timeout(struct watchdog_device *wdt_dev, 8722e1b8f6SCarlo Caione unsigned int timeout) 8822e1b8f6SCarlo Caione { 8922e1b8f6SCarlo Caione struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); 9022e1b8f6SCarlo Caione u32 reg; 9122e1b8f6SCarlo Caione 9222e1b8f6SCarlo Caione reg = readl(meson_wdt->wdt_base + MESON_WDT_TC); 93943bf1f6SCarlo Caione reg &= ~meson_wdt->data->terminal_count_mask; 94943bf1f6SCarlo Caione reg |= MESON_SEC_TO_TC(timeout, meson_wdt->data->count_unit); 9522e1b8f6SCarlo Caione writel(reg, meson_wdt->wdt_base + MESON_WDT_TC); 9622e1b8f6SCarlo Caione } 9722e1b8f6SCarlo Caione 9822e1b8f6SCarlo Caione static int meson_wdt_set_timeout(struct watchdog_device *wdt_dev, 9922e1b8f6SCarlo Caione unsigned int timeout) 10022e1b8f6SCarlo Caione { 10122e1b8f6SCarlo Caione wdt_dev->timeout = timeout; 10222e1b8f6SCarlo Caione 10322e1b8f6SCarlo Caione meson_wdt_change_timeout(wdt_dev, timeout); 10422e1b8f6SCarlo Caione meson_wdt_ping(wdt_dev); 10522e1b8f6SCarlo Caione 10622e1b8f6SCarlo Caione return 0; 10722e1b8f6SCarlo Caione } 10822e1b8f6SCarlo Caione 10922e1b8f6SCarlo Caione static int meson_wdt_stop(struct watchdog_device *wdt_dev) 11022e1b8f6SCarlo Caione { 11122e1b8f6SCarlo Caione struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); 11222e1b8f6SCarlo Caione u32 reg; 11322e1b8f6SCarlo Caione 11422e1b8f6SCarlo Caione reg = readl(meson_wdt->wdt_base + MESON_WDT_TC); 115943bf1f6SCarlo Caione reg &= ~meson_wdt->data->enable; 11622e1b8f6SCarlo Caione writel(reg, meson_wdt->wdt_base + MESON_WDT_TC); 11722e1b8f6SCarlo Caione 11822e1b8f6SCarlo Caione return 0; 11922e1b8f6SCarlo Caione } 12022e1b8f6SCarlo Caione 12122e1b8f6SCarlo Caione static int meson_wdt_start(struct watchdog_device *wdt_dev) 12222e1b8f6SCarlo Caione { 12322e1b8f6SCarlo Caione struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); 12422e1b8f6SCarlo Caione u32 reg; 12522e1b8f6SCarlo Caione 12622e1b8f6SCarlo Caione meson_wdt_change_timeout(wdt_dev, meson_wdt->wdt_dev.timeout); 12722e1b8f6SCarlo Caione meson_wdt_ping(wdt_dev); 12822e1b8f6SCarlo Caione 12922e1b8f6SCarlo Caione reg = readl(meson_wdt->wdt_base + MESON_WDT_TC); 130943bf1f6SCarlo Caione reg |= meson_wdt->data->enable; 13122e1b8f6SCarlo Caione writel(reg, meson_wdt->wdt_base + MESON_WDT_TC); 13222e1b8f6SCarlo Caione 13322e1b8f6SCarlo Caione return 0; 13422e1b8f6SCarlo Caione } 13522e1b8f6SCarlo Caione 13622e1b8f6SCarlo Caione static const struct watchdog_info meson_wdt_info = { 13722e1b8f6SCarlo Caione .identity = DRV_NAME, 13822e1b8f6SCarlo Caione .options = WDIOF_SETTIMEOUT | 13922e1b8f6SCarlo Caione WDIOF_KEEPALIVEPING | 14022e1b8f6SCarlo Caione WDIOF_MAGICCLOSE, 14122e1b8f6SCarlo Caione }; 14222e1b8f6SCarlo Caione 14322e1b8f6SCarlo Caione static const struct watchdog_ops meson_wdt_ops = { 14422e1b8f6SCarlo Caione .owner = THIS_MODULE, 14522e1b8f6SCarlo Caione .start = meson_wdt_start, 14622e1b8f6SCarlo Caione .stop = meson_wdt_stop, 14722e1b8f6SCarlo Caione .ping = meson_wdt_ping, 14822e1b8f6SCarlo Caione .set_timeout = meson_wdt_set_timeout, 1491b6fd59aSDamien Riegel .restart = meson_wdt_restart, 15022e1b8f6SCarlo Caione }; 15122e1b8f6SCarlo Caione 152943bf1f6SCarlo Caione static const struct of_device_id meson_wdt_dt_ids[] = { 153943bf1f6SCarlo Caione { .compatible = "amlogic,meson6-wdt", .data = &meson6_wdt_data }, 15443a64e81SMartin Blumenstingl { .compatible = "amlogic,meson8-wdt", .data = &meson6_wdt_data }, 15571388840SCarlo Caione { .compatible = "amlogic,meson8b-wdt", .data = &meson8b_wdt_data }, 15643a64e81SMartin Blumenstingl { .compatible = "amlogic,meson8m2-wdt", .data = &meson8b_wdt_data }, 157943bf1f6SCarlo Caione { /* sentinel */ } 158943bf1f6SCarlo Caione }; 159943bf1f6SCarlo Caione MODULE_DEVICE_TABLE(of, meson_wdt_dt_ids); 160943bf1f6SCarlo Caione 16122e1b8f6SCarlo Caione static int meson_wdt_probe(struct platform_device *pdev) 16222e1b8f6SCarlo Caione { 163dd1c66e2SGuenter Roeck struct device *dev = &pdev->dev; 16422e1b8f6SCarlo Caione struct meson_wdt_dev *meson_wdt; 16522e1b8f6SCarlo Caione int err; 16622e1b8f6SCarlo Caione 167dd1c66e2SGuenter Roeck meson_wdt = devm_kzalloc(dev, sizeof(*meson_wdt), GFP_KERNEL); 16822e1b8f6SCarlo Caione if (!meson_wdt) 16922e1b8f6SCarlo Caione return -ENOMEM; 17022e1b8f6SCarlo Caione 1710f0a6a28SGuenter Roeck meson_wdt->wdt_base = devm_platform_ioremap_resource(pdev, 0); 17222e1b8f6SCarlo Caione if (IS_ERR(meson_wdt->wdt_base)) 17322e1b8f6SCarlo Caione return PTR_ERR(meson_wdt->wdt_base); 17422e1b8f6SCarlo Caione 175*0a1186e4STian Tao meson_wdt->data = device_get_match_data(dev); 176943bf1f6SCarlo Caione 177dd1c66e2SGuenter Roeck meson_wdt->wdt_dev.parent = dev; 17822e1b8f6SCarlo Caione meson_wdt->wdt_dev.info = &meson_wdt_info; 17922e1b8f6SCarlo Caione meson_wdt->wdt_dev.ops = &meson_wdt_ops; 180943bf1f6SCarlo Caione meson_wdt->wdt_dev.max_timeout = 181943bf1f6SCarlo Caione meson_wdt->data->terminal_count_mask / meson_wdt->data->count_unit; 18222e1b8f6SCarlo Caione meson_wdt->wdt_dev.min_timeout = MESON_WDT_MIN_TIMEOUT; 183943bf1f6SCarlo Caione meson_wdt->wdt_dev.timeout = min_t(unsigned int, 184943bf1f6SCarlo Caione MESON_WDT_TIMEOUT, 185943bf1f6SCarlo Caione meson_wdt->wdt_dev.max_timeout); 18622e1b8f6SCarlo Caione 18722e1b8f6SCarlo Caione watchdog_set_drvdata(&meson_wdt->wdt_dev, meson_wdt); 18822e1b8f6SCarlo Caione 189dd1c66e2SGuenter Roeck watchdog_init_timeout(&meson_wdt->wdt_dev, timeout, dev); 19022e1b8f6SCarlo Caione watchdog_set_nowayout(&meson_wdt->wdt_dev, nowayout); 1911b6fd59aSDamien Riegel watchdog_set_restart_priority(&meson_wdt->wdt_dev, 128); 19222e1b8f6SCarlo Caione 19322e1b8f6SCarlo Caione meson_wdt_stop(&meson_wdt->wdt_dev); 19422e1b8f6SCarlo Caione 195c8841a60SGuenter Roeck watchdog_stop_on_reboot(&meson_wdt->wdt_dev); 196dd1c66e2SGuenter Roeck err = devm_watchdog_register_device(dev, &meson_wdt->wdt_dev); 19722e1b8f6SCarlo Caione if (err) 19822e1b8f6SCarlo Caione return err; 19922e1b8f6SCarlo Caione 200dd1c66e2SGuenter Roeck dev_info(dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)", 20122e1b8f6SCarlo Caione meson_wdt->wdt_dev.timeout, nowayout); 20222e1b8f6SCarlo Caione 20322e1b8f6SCarlo Caione return 0; 20422e1b8f6SCarlo Caione } 20522e1b8f6SCarlo Caione 20622e1b8f6SCarlo Caione static struct platform_driver meson_wdt_driver = { 20722e1b8f6SCarlo Caione .probe = meson_wdt_probe, 20822e1b8f6SCarlo Caione .driver = { 20922e1b8f6SCarlo Caione .name = DRV_NAME, 21022e1b8f6SCarlo Caione .of_match_table = meson_wdt_dt_ids, 21122e1b8f6SCarlo Caione }, 21222e1b8f6SCarlo Caione }; 21322e1b8f6SCarlo Caione 21422e1b8f6SCarlo Caione module_platform_driver(meson_wdt_driver); 21522e1b8f6SCarlo Caione 21622e1b8f6SCarlo Caione module_param(timeout, uint, 0); 21722e1b8f6SCarlo Caione MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds"); 21822e1b8f6SCarlo Caione 21922e1b8f6SCarlo Caione module_param(nowayout, bool, 0); 22022e1b8f6SCarlo Caione MODULE_PARM_DESC(nowayout, 22122e1b8f6SCarlo Caione "Watchdog cannot be stopped once started (default=" 22222e1b8f6SCarlo Caione __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 22322e1b8f6SCarlo Caione 22422e1b8f6SCarlo Caione MODULE_LICENSE("GPL"); 22522e1b8f6SCarlo Caione MODULE_AUTHOR("Carlo Caione <carlo@caione.org>"); 22622e1b8f6SCarlo Caione MODULE_DESCRIPTION("Meson Watchdog Timer Driver"); 227