Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
0002 /*
0003  * Copyright (c) 2016 BayLibre, SAS.
0004  * Author: Neil Armstrong <narmstrong@baylibre.com>
0005  *
0006  */
0007 #include <linux/clk.h>
0008 #include <linux/err.h>
0009 #include <linux/io.h>
0010 #include <linux/module.h>
0011 #include <linux/of.h>
0012 #include <linux/platform_device.h>
0013 #include <linux/slab.h>
0014 #include <linux/types.h>
0015 #include <linux/watchdog.h>
0016 
0017 #define DEFAULT_TIMEOUT 30  /* seconds */
0018 
0019 #define GXBB_WDT_CTRL_REG           0x0
0020 #define GXBB_WDT_TCNT_REG           0x8
0021 #define GXBB_WDT_RSET_REG           0xc
0022 
0023 #define GXBB_WDT_CTRL_CLKDIV_EN         BIT(25)
0024 #define GXBB_WDT_CTRL_CLK_EN            BIT(24)
0025 #define GXBB_WDT_CTRL_EE_RESET          BIT(21)
0026 #define GXBB_WDT_CTRL_EN            BIT(18)
0027 #define GXBB_WDT_CTRL_DIV_MASK          (BIT(18) - 1)
0028 
0029 #define GXBB_WDT_TCNT_SETUP_MASK        (BIT(16) - 1)
0030 #define GXBB_WDT_TCNT_CNT_SHIFT         16
0031 
0032 static bool nowayout = WATCHDOG_NOWAYOUT;
0033 module_param(nowayout, bool, 0);
0034 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started default="
0035          __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
0036 
0037 static unsigned int timeout;
0038 module_param(timeout, uint, 0);
0039 MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds="
0040          __MODULE_STRING(DEFAULT_TIMEOUT) ")");
0041 
0042 struct meson_gxbb_wdt {
0043     void __iomem *reg_base;
0044     struct watchdog_device wdt_dev;
0045     struct clk *clk;
0046 };
0047 
0048 static int meson_gxbb_wdt_start(struct watchdog_device *wdt_dev)
0049 {
0050     struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
0051 
0052     writel(readl(data->reg_base + GXBB_WDT_CTRL_REG) | GXBB_WDT_CTRL_EN,
0053            data->reg_base + GXBB_WDT_CTRL_REG);
0054 
0055     return 0;
0056 }
0057 
0058 static int meson_gxbb_wdt_stop(struct watchdog_device *wdt_dev)
0059 {
0060     struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
0061 
0062     writel(readl(data->reg_base + GXBB_WDT_CTRL_REG) & ~GXBB_WDT_CTRL_EN,
0063            data->reg_base + GXBB_WDT_CTRL_REG);
0064 
0065     return 0;
0066 }
0067 
0068 static int meson_gxbb_wdt_ping(struct watchdog_device *wdt_dev)
0069 {
0070     struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
0071 
0072     writel(0, data->reg_base + GXBB_WDT_RSET_REG);
0073 
0074     return 0;
0075 }
0076 
0077 static int meson_gxbb_wdt_set_timeout(struct watchdog_device *wdt_dev,
0078                       unsigned int timeout)
0079 {
0080     struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
0081     unsigned long tcnt = timeout * 1000;
0082 
0083     if (tcnt > GXBB_WDT_TCNT_SETUP_MASK)
0084         tcnt = GXBB_WDT_TCNT_SETUP_MASK;
0085 
0086     wdt_dev->timeout = timeout;
0087 
0088     meson_gxbb_wdt_ping(wdt_dev);
0089 
0090     writel(tcnt, data->reg_base + GXBB_WDT_TCNT_REG);
0091 
0092     return 0;
0093 }
0094 
0095 static unsigned int meson_gxbb_wdt_get_timeleft(struct watchdog_device *wdt_dev)
0096 {
0097     struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
0098     unsigned long reg;
0099 
0100     reg = readl(data->reg_base + GXBB_WDT_TCNT_REG);
0101 
0102     return ((reg & GXBB_WDT_TCNT_SETUP_MASK) -
0103         (reg >> GXBB_WDT_TCNT_CNT_SHIFT)) / 1000;
0104 }
0105 
0106 static const struct watchdog_ops meson_gxbb_wdt_ops = {
0107     .start = meson_gxbb_wdt_start,
0108     .stop = meson_gxbb_wdt_stop,
0109     .ping = meson_gxbb_wdt_ping,
0110     .set_timeout = meson_gxbb_wdt_set_timeout,
0111     .get_timeleft = meson_gxbb_wdt_get_timeleft,
0112 };
0113 
0114 static const struct watchdog_info meson_gxbb_wdt_info = {
0115     .identity = "Meson GXBB Watchdog",
0116     .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
0117 };
0118 
0119 static int __maybe_unused meson_gxbb_wdt_resume(struct device *dev)
0120 {
0121     struct meson_gxbb_wdt *data = dev_get_drvdata(dev);
0122 
0123     if (watchdog_active(&data->wdt_dev))
0124         meson_gxbb_wdt_start(&data->wdt_dev);
0125 
0126     return 0;
0127 }
0128 
0129 static int __maybe_unused meson_gxbb_wdt_suspend(struct device *dev)
0130 {
0131     struct meson_gxbb_wdt *data = dev_get_drvdata(dev);
0132 
0133     if (watchdog_active(&data->wdt_dev))
0134         meson_gxbb_wdt_stop(&data->wdt_dev);
0135 
0136     return 0;
0137 }
0138 
0139 static const struct dev_pm_ops meson_gxbb_wdt_pm_ops = {
0140     SET_SYSTEM_SLEEP_PM_OPS(meson_gxbb_wdt_suspend, meson_gxbb_wdt_resume)
0141 };
0142 
0143 static const struct of_device_id meson_gxbb_wdt_dt_ids[] = {
0144      { .compatible = "amlogic,meson-gxbb-wdt", },
0145      { /* sentinel */ },
0146 };
0147 MODULE_DEVICE_TABLE(of, meson_gxbb_wdt_dt_ids);
0148 
0149 static void meson_clk_disable_unprepare(void *data)
0150 {
0151     clk_disable_unprepare(data);
0152 }
0153 
0154 static int meson_gxbb_wdt_probe(struct platform_device *pdev)
0155 {
0156     struct device *dev = &pdev->dev;
0157     struct meson_gxbb_wdt *data;
0158     int ret;
0159 
0160     data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
0161     if (!data)
0162         return -ENOMEM;
0163 
0164     data->reg_base = devm_platform_ioremap_resource(pdev, 0);
0165     if (IS_ERR(data->reg_base))
0166         return PTR_ERR(data->reg_base);
0167 
0168     data->clk = devm_clk_get(dev, NULL);
0169     if (IS_ERR(data->clk))
0170         return PTR_ERR(data->clk);
0171 
0172     ret = clk_prepare_enable(data->clk);
0173     if (ret)
0174         return ret;
0175     ret = devm_add_action_or_reset(dev, meson_clk_disable_unprepare,
0176                        data->clk);
0177     if (ret)
0178         return ret;
0179 
0180     platform_set_drvdata(pdev, data);
0181 
0182     data->wdt_dev.parent = dev;
0183     data->wdt_dev.info = &meson_gxbb_wdt_info;
0184     data->wdt_dev.ops = &meson_gxbb_wdt_ops;
0185     data->wdt_dev.max_hw_heartbeat_ms = GXBB_WDT_TCNT_SETUP_MASK;
0186     data->wdt_dev.min_timeout = 1;
0187     data->wdt_dev.timeout = DEFAULT_TIMEOUT;
0188     watchdog_init_timeout(&data->wdt_dev, timeout, dev);
0189     watchdog_set_nowayout(&data->wdt_dev, nowayout);
0190     watchdog_set_drvdata(&data->wdt_dev, data);
0191 
0192     /* Setup with 1ms timebase */
0193     writel(((clk_get_rate(data->clk) / 1000) & GXBB_WDT_CTRL_DIV_MASK) |
0194         GXBB_WDT_CTRL_EE_RESET |
0195         GXBB_WDT_CTRL_CLK_EN |
0196         GXBB_WDT_CTRL_CLKDIV_EN,
0197         data->reg_base + GXBB_WDT_CTRL_REG);
0198 
0199     meson_gxbb_wdt_set_timeout(&data->wdt_dev, data->wdt_dev.timeout);
0200 
0201     return devm_watchdog_register_device(dev, &data->wdt_dev);
0202 }
0203 
0204 static struct platform_driver meson_gxbb_wdt_driver = {
0205     .probe  = meson_gxbb_wdt_probe,
0206     .driver = {
0207         .name = "meson-gxbb-wdt",
0208         .pm = &meson_gxbb_wdt_pm_ops,
0209         .of_match_table = meson_gxbb_wdt_dt_ids,
0210     },
0211 };
0212 
0213 module_platform_driver(meson_gxbb_wdt_driver);
0214 
0215 MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
0216 MODULE_DESCRIPTION("Amlogic Meson GXBB Watchdog timer driver");
0217 MODULE_LICENSE("Dual BSD/GPL");