1*a0d261ccSBagas Sanjaya // SPDX-License-Identifier: GPL-2.0+
22bdf6acbSAndreas Färber /*
32bdf6acbSAndreas Färber * Realtek RTD129x watchdog
42bdf6acbSAndreas Färber *
52bdf6acbSAndreas Färber * Copyright (c) 2017 Andreas Färber
62bdf6acbSAndreas Färber *
72bdf6acbSAndreas Färber */
82bdf6acbSAndreas Färber
92bdf6acbSAndreas Färber #include <linux/bitops.h>
102bdf6acbSAndreas Färber #include <linux/clk.h>
112bdf6acbSAndreas Färber #include <linux/io.h>
122bdf6acbSAndreas Färber #include <linux/of.h>
132bdf6acbSAndreas Färber #include <linux/of_address.h>
142bdf6acbSAndreas Färber #include <linux/platform_device.h>
152bdf6acbSAndreas Färber #include <linux/watchdog.h>
162bdf6acbSAndreas Färber
172bdf6acbSAndreas Färber #define RTD119X_TCWCR 0x0
182bdf6acbSAndreas Färber #define RTD119X_TCWTR 0x4
192bdf6acbSAndreas Färber #define RTD119X_TCWOV 0xc
202bdf6acbSAndreas Färber
212bdf6acbSAndreas Färber #define RTD119X_TCWCR_WDEN_DISABLED 0xa5
222bdf6acbSAndreas Färber #define RTD119X_TCWCR_WDEN_ENABLED 0xff
232bdf6acbSAndreas Färber #define RTD119X_TCWCR_WDEN_MASK 0xff
242bdf6acbSAndreas Färber
252bdf6acbSAndreas Färber #define RTD119X_TCWTR_WDCLR BIT(0)
262bdf6acbSAndreas Färber
272bdf6acbSAndreas Färber struct rtd119x_watchdog_device {
282bdf6acbSAndreas Färber struct watchdog_device wdt_dev;
292bdf6acbSAndreas Färber void __iomem *base;
302bdf6acbSAndreas Färber struct clk *clk;
312bdf6acbSAndreas Färber };
322bdf6acbSAndreas Färber
rtd119x_wdt_start(struct watchdog_device * wdev)332bdf6acbSAndreas Färber static int rtd119x_wdt_start(struct watchdog_device *wdev)
342bdf6acbSAndreas Färber {
352bdf6acbSAndreas Färber struct rtd119x_watchdog_device *data = watchdog_get_drvdata(wdev);
362bdf6acbSAndreas Färber u32 val;
372bdf6acbSAndreas Färber
382bdf6acbSAndreas Färber val = readl_relaxed(data->base + RTD119X_TCWCR);
392bdf6acbSAndreas Färber val &= ~RTD119X_TCWCR_WDEN_MASK;
402bdf6acbSAndreas Färber val |= RTD119X_TCWCR_WDEN_ENABLED;
412bdf6acbSAndreas Färber writel(val, data->base + RTD119X_TCWCR);
422bdf6acbSAndreas Färber
432bdf6acbSAndreas Färber return 0;
442bdf6acbSAndreas Färber }
452bdf6acbSAndreas Färber
rtd119x_wdt_stop(struct watchdog_device * wdev)462bdf6acbSAndreas Färber static int rtd119x_wdt_stop(struct watchdog_device *wdev)
472bdf6acbSAndreas Färber {
482bdf6acbSAndreas Färber struct rtd119x_watchdog_device *data = watchdog_get_drvdata(wdev);
492bdf6acbSAndreas Färber u32 val;
502bdf6acbSAndreas Färber
512bdf6acbSAndreas Färber val = readl_relaxed(data->base + RTD119X_TCWCR);
522bdf6acbSAndreas Färber val &= ~RTD119X_TCWCR_WDEN_MASK;
532bdf6acbSAndreas Färber val |= RTD119X_TCWCR_WDEN_DISABLED;
542bdf6acbSAndreas Färber writel(val, data->base + RTD119X_TCWCR);
552bdf6acbSAndreas Färber
562bdf6acbSAndreas Färber return 0;
572bdf6acbSAndreas Färber }
582bdf6acbSAndreas Färber
rtd119x_wdt_ping(struct watchdog_device * wdev)592bdf6acbSAndreas Färber static int rtd119x_wdt_ping(struct watchdog_device *wdev)
602bdf6acbSAndreas Färber {
612bdf6acbSAndreas Färber struct rtd119x_watchdog_device *data = watchdog_get_drvdata(wdev);
622bdf6acbSAndreas Färber
632bdf6acbSAndreas Färber writel_relaxed(RTD119X_TCWTR_WDCLR, data->base + RTD119X_TCWTR);
642bdf6acbSAndreas Färber
652bdf6acbSAndreas Färber return rtd119x_wdt_start(wdev);
662bdf6acbSAndreas Färber }
672bdf6acbSAndreas Färber
rtd119x_wdt_set_timeout(struct watchdog_device * wdev,unsigned int val)682bdf6acbSAndreas Färber static int rtd119x_wdt_set_timeout(struct watchdog_device *wdev, unsigned int val)
692bdf6acbSAndreas Färber {
702bdf6acbSAndreas Färber struct rtd119x_watchdog_device *data = watchdog_get_drvdata(wdev);
712bdf6acbSAndreas Färber
722bdf6acbSAndreas Färber writel(val * clk_get_rate(data->clk), data->base + RTD119X_TCWOV);
732bdf6acbSAndreas Färber
742bdf6acbSAndreas Färber data->wdt_dev.timeout = val;
752bdf6acbSAndreas Färber
762bdf6acbSAndreas Färber return 0;
772bdf6acbSAndreas Färber }
782bdf6acbSAndreas Färber
792bdf6acbSAndreas Färber static const struct watchdog_ops rtd119x_wdt_ops = {
802bdf6acbSAndreas Färber .owner = THIS_MODULE,
812bdf6acbSAndreas Färber .start = rtd119x_wdt_start,
822bdf6acbSAndreas Färber .stop = rtd119x_wdt_stop,
832bdf6acbSAndreas Färber .ping = rtd119x_wdt_ping,
842bdf6acbSAndreas Färber .set_timeout = rtd119x_wdt_set_timeout,
852bdf6acbSAndreas Färber };
862bdf6acbSAndreas Färber
872bdf6acbSAndreas Färber static const struct watchdog_info rtd119x_wdt_info = {
882bdf6acbSAndreas Färber .identity = "rtd119x-wdt",
892bdf6acbSAndreas Färber .options = 0,
902bdf6acbSAndreas Färber };
912bdf6acbSAndreas Färber
922bdf6acbSAndreas Färber static const struct of_device_id rtd119x_wdt_dt_ids[] = {
932bdf6acbSAndreas Färber { .compatible = "realtek,rtd1295-watchdog" },
942bdf6acbSAndreas Färber { }
952bdf6acbSAndreas Färber };
962bdf6acbSAndreas Färber
rtd119x_wdt_probe(struct platform_device * pdev)972bdf6acbSAndreas Färber static int rtd119x_wdt_probe(struct platform_device *pdev)
982bdf6acbSAndreas Färber {
99553140a0SGuenter Roeck struct device *dev = &pdev->dev;
1002bdf6acbSAndreas Färber struct rtd119x_watchdog_device *data;
1012bdf6acbSAndreas Färber
102553140a0SGuenter Roeck data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
1032bdf6acbSAndreas Färber if (!data)
1042bdf6acbSAndreas Färber return -ENOMEM;
1052bdf6acbSAndreas Färber
1060f0a6a28SGuenter Roeck data->base = devm_platform_ioremap_resource(pdev, 0);
1072bdf6acbSAndreas Färber if (IS_ERR(data->base))
1082bdf6acbSAndreas Färber return PTR_ERR(data->base);
1092bdf6acbSAndreas Färber
1106cc0768bSChristophe JAILLET data->clk = devm_clk_get_enabled(dev, NULL);
1112bdf6acbSAndreas Färber if (IS_ERR(data->clk))
1122bdf6acbSAndreas Färber return PTR_ERR(data->clk);
1132bdf6acbSAndreas Färber
1142bdf6acbSAndreas Färber data->wdt_dev.info = &rtd119x_wdt_info;
1152bdf6acbSAndreas Färber data->wdt_dev.ops = &rtd119x_wdt_ops;
1162bdf6acbSAndreas Färber data->wdt_dev.timeout = 120;
1172bdf6acbSAndreas Färber data->wdt_dev.max_timeout = 0xffffffff / clk_get_rate(data->clk);
1182bdf6acbSAndreas Färber data->wdt_dev.min_timeout = 1;
119553140a0SGuenter Roeck data->wdt_dev.parent = dev;
1202bdf6acbSAndreas Färber
1212bdf6acbSAndreas Färber watchdog_stop_on_reboot(&data->wdt_dev);
1222bdf6acbSAndreas Färber watchdog_set_drvdata(&data->wdt_dev, data);
1232bdf6acbSAndreas Färber platform_set_drvdata(pdev, data);
1242bdf6acbSAndreas Färber
1252bdf6acbSAndreas Färber writel_relaxed(RTD119X_TCWTR_WDCLR, data->base + RTD119X_TCWTR);
1262bdf6acbSAndreas Färber rtd119x_wdt_set_timeout(&data->wdt_dev, data->wdt_dev.timeout);
1272bdf6acbSAndreas Färber rtd119x_wdt_stop(&data->wdt_dev);
1282bdf6acbSAndreas Färber
129553140a0SGuenter Roeck return devm_watchdog_register_device(dev, &data->wdt_dev);
1302bdf6acbSAndreas Färber }
1312bdf6acbSAndreas Färber
1322bdf6acbSAndreas Färber static struct platform_driver rtd119x_wdt_driver = {
1332bdf6acbSAndreas Färber .probe = rtd119x_wdt_probe,
1342bdf6acbSAndreas Färber .driver = {
1352bdf6acbSAndreas Färber .name = "rtd1295-watchdog",
1362bdf6acbSAndreas Färber .of_match_table = rtd119x_wdt_dt_ids,
1372bdf6acbSAndreas Färber },
1382bdf6acbSAndreas Färber };
1392bdf6acbSAndreas Färber builtin_platform_driver(rtd119x_wdt_driver);
140