Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  *      sunxi Watchdog Driver
0004  *
0005  *      Copyright (c) 2013 Carlo Caione
0006  *                    2012 Henrik Nordstrom
0007  *
0008  *      Based on xen_wdt.c
0009  *      (c) Copyright 2010 Novell, Inc.
0010  */
0011 
0012 #include <linux/clk.h>
0013 #include <linux/delay.h>
0014 #include <linux/err.h>
0015 #include <linux/init.h>
0016 #include <linux/io.h>
0017 #include <linux/kernel.h>
0018 #include <linux/module.h>
0019 #include <linux/moduleparam.h>
0020 #include <linux/of.h>
0021 #include <linux/of_device.h>
0022 #include <linux/platform_device.h>
0023 #include <linux/types.h>
0024 #include <linux/watchdog.h>
0025 
0026 #define WDT_MAX_TIMEOUT         16
0027 #define WDT_MIN_TIMEOUT         1
0028 #define WDT_TIMEOUT_MASK        0x0F
0029 
0030 #define WDT_CTRL_RELOAD         ((1 << 0) | (0x0a57 << 1))
0031 
0032 #define WDT_MODE_EN             (1 << 0)
0033 
0034 #define DRV_NAME        "sunxi-wdt"
0035 #define DRV_VERSION     "1.0"
0036 
0037 static bool nowayout = WATCHDOG_NOWAYOUT;
0038 static unsigned int timeout;
0039 
0040 /*
0041  * This structure stores the register offsets for different variants
0042  * of Allwinner's watchdog hardware.
0043  */
0044 struct sunxi_wdt_reg {
0045     u8 wdt_ctrl;
0046     u8 wdt_cfg;
0047     u8 wdt_mode;
0048     u8 wdt_timeout_shift;
0049     u8 wdt_reset_mask;
0050     u8 wdt_reset_val;
0051     u32 wdt_key_val;
0052 };
0053 
0054 struct sunxi_wdt_dev {
0055     struct watchdog_device wdt_dev;
0056     void __iomem *wdt_base;
0057     const struct sunxi_wdt_reg *wdt_regs;
0058 };
0059 
0060 /*
0061  * wdt_timeout_map maps the watchdog timer interval value in seconds to
0062  * the value of the register WDT_MODE at bits .wdt_timeout_shift ~ +3
0063  *
0064  * [timeout seconds] = register value
0065  *
0066  */
0067 
0068 static const int wdt_timeout_map[] = {
0069     [1] = 0x1,  /* 1s  */
0070     [2] = 0x2,  /* 2s  */
0071     [3] = 0x3,  /* 3s  */
0072     [4] = 0x4,  /* 4s  */
0073     [5] = 0x5,  /* 5s  */
0074     [6] = 0x6,  /* 6s  */
0075     [8] = 0x7,  /* 8s  */
0076     [10] = 0x8, /* 10s */
0077     [12] = 0x9, /* 12s */
0078     [14] = 0xA, /* 14s */
0079     [16] = 0xB, /* 16s */
0080 };
0081 
0082 
0083 static int sunxi_wdt_restart(struct watchdog_device *wdt_dev,
0084                  unsigned long action, void *data)
0085 {
0086     struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
0087     void __iomem *wdt_base = sunxi_wdt->wdt_base;
0088     const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
0089     u32 val;
0090 
0091     /* Set system reset function */
0092     val = readl(wdt_base + regs->wdt_cfg);
0093     val &= ~(regs->wdt_reset_mask);
0094     val |= regs->wdt_reset_val;
0095     val |= regs->wdt_key_val;
0096     writel(val, wdt_base + regs->wdt_cfg);
0097 
0098     /* Set lowest timeout and enable watchdog */
0099     val = readl(wdt_base + regs->wdt_mode);
0100     val &= ~(WDT_TIMEOUT_MASK << regs->wdt_timeout_shift);
0101     val |= WDT_MODE_EN;
0102     val |= regs->wdt_key_val;
0103     writel(val, wdt_base + regs->wdt_mode);
0104 
0105     /*
0106      * Restart the watchdog. The default (and lowest) interval
0107      * value for the watchdog is 0.5s.
0108      */
0109     writel(WDT_CTRL_RELOAD, wdt_base + regs->wdt_ctrl);
0110 
0111     while (1) {
0112         mdelay(5);
0113         val = readl(wdt_base + regs->wdt_mode);
0114         val |= WDT_MODE_EN;
0115         val |= regs->wdt_key_val;
0116         writel(val, wdt_base + regs->wdt_mode);
0117     }
0118     return 0;
0119 }
0120 
0121 static int sunxi_wdt_ping(struct watchdog_device *wdt_dev)
0122 {
0123     struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
0124     void __iomem *wdt_base = sunxi_wdt->wdt_base;
0125     const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
0126 
0127     writel(WDT_CTRL_RELOAD, wdt_base + regs->wdt_ctrl);
0128 
0129     return 0;
0130 }
0131 
0132 static int sunxi_wdt_set_timeout(struct watchdog_device *wdt_dev,
0133         unsigned int timeout)
0134 {
0135     struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
0136     void __iomem *wdt_base = sunxi_wdt->wdt_base;
0137     const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
0138     u32 reg;
0139 
0140     if (wdt_timeout_map[timeout] == 0)
0141         timeout++;
0142 
0143     sunxi_wdt->wdt_dev.timeout = timeout;
0144 
0145     reg = readl(wdt_base + regs->wdt_mode);
0146     reg &= ~(WDT_TIMEOUT_MASK << regs->wdt_timeout_shift);
0147     reg |= wdt_timeout_map[timeout] << regs->wdt_timeout_shift;
0148     reg |= regs->wdt_key_val;
0149     writel(reg, wdt_base + regs->wdt_mode);
0150 
0151     sunxi_wdt_ping(wdt_dev);
0152 
0153     return 0;
0154 }
0155 
0156 static int sunxi_wdt_stop(struct watchdog_device *wdt_dev)
0157 {
0158     struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
0159     void __iomem *wdt_base = sunxi_wdt->wdt_base;
0160     const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
0161 
0162     writel(regs->wdt_key_val, wdt_base + regs->wdt_mode);
0163 
0164     return 0;
0165 }
0166 
0167 static int sunxi_wdt_start(struct watchdog_device *wdt_dev)
0168 {
0169     u32 reg;
0170     struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
0171     void __iomem *wdt_base = sunxi_wdt->wdt_base;
0172     const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
0173     int ret;
0174 
0175     ret = sunxi_wdt_set_timeout(&sunxi_wdt->wdt_dev,
0176             sunxi_wdt->wdt_dev.timeout);
0177     if (ret < 0)
0178         return ret;
0179 
0180     /* Set system reset function */
0181     reg = readl(wdt_base + regs->wdt_cfg);
0182     reg &= ~(regs->wdt_reset_mask);
0183     reg |= regs->wdt_reset_val;
0184     reg |= regs->wdt_key_val;
0185     writel(reg, wdt_base + regs->wdt_cfg);
0186 
0187     /* Enable watchdog */
0188     reg = readl(wdt_base + regs->wdt_mode);
0189     reg |= WDT_MODE_EN;
0190     reg |= regs->wdt_key_val;
0191     writel(reg, wdt_base + regs->wdt_mode);
0192 
0193     return 0;
0194 }
0195 
0196 static const struct watchdog_info sunxi_wdt_info = {
0197     .identity   = DRV_NAME,
0198     .options    = WDIOF_SETTIMEOUT |
0199               WDIOF_KEEPALIVEPING |
0200               WDIOF_MAGICCLOSE,
0201 };
0202 
0203 static const struct watchdog_ops sunxi_wdt_ops = {
0204     .owner      = THIS_MODULE,
0205     .start      = sunxi_wdt_start,
0206     .stop       = sunxi_wdt_stop,
0207     .ping       = sunxi_wdt_ping,
0208     .set_timeout    = sunxi_wdt_set_timeout,
0209     .restart    = sunxi_wdt_restart,
0210 };
0211 
0212 static const struct sunxi_wdt_reg sun4i_wdt_reg = {
0213     .wdt_ctrl = 0x00,
0214     .wdt_cfg = 0x04,
0215     .wdt_mode = 0x04,
0216     .wdt_timeout_shift = 3,
0217     .wdt_reset_mask = 0x02,
0218     .wdt_reset_val = 0x02,
0219 };
0220 
0221 static const struct sunxi_wdt_reg sun6i_wdt_reg = {
0222     .wdt_ctrl = 0x10,
0223     .wdt_cfg = 0x14,
0224     .wdt_mode = 0x18,
0225     .wdt_timeout_shift = 4,
0226     .wdt_reset_mask = 0x03,
0227     .wdt_reset_val = 0x01,
0228 };
0229 
0230 static const struct sunxi_wdt_reg sun20i_wdt_reg = {
0231     .wdt_ctrl = 0x10,
0232     .wdt_cfg = 0x14,
0233     .wdt_mode = 0x18,
0234     .wdt_timeout_shift = 4,
0235     .wdt_reset_mask = 0x03,
0236     .wdt_reset_val = 0x01,
0237     .wdt_key_val = 0x16aa0000,
0238 };
0239 
0240 static const struct of_device_id sunxi_wdt_dt_ids[] = {
0241     { .compatible = "allwinner,sun4i-a10-wdt", .data = &sun4i_wdt_reg },
0242     { .compatible = "allwinner,sun6i-a31-wdt", .data = &sun6i_wdt_reg },
0243     { .compatible = "allwinner,sun20i-d1-wdt", .data = &sun20i_wdt_reg },
0244     { /* sentinel */ }
0245 };
0246 MODULE_DEVICE_TABLE(of, sunxi_wdt_dt_ids);
0247 
0248 static int sunxi_wdt_probe(struct platform_device *pdev)
0249 {
0250     struct device *dev = &pdev->dev;
0251     struct sunxi_wdt_dev *sunxi_wdt;
0252     int err;
0253 
0254     sunxi_wdt = devm_kzalloc(dev, sizeof(*sunxi_wdt), GFP_KERNEL);
0255     if (!sunxi_wdt)
0256         return -ENOMEM;
0257 
0258     sunxi_wdt->wdt_regs = of_device_get_match_data(dev);
0259     if (!sunxi_wdt->wdt_regs)
0260         return -ENODEV;
0261 
0262     sunxi_wdt->wdt_base = devm_platform_ioremap_resource(pdev, 0);
0263     if (IS_ERR(sunxi_wdt->wdt_base))
0264         return PTR_ERR(sunxi_wdt->wdt_base);
0265 
0266     sunxi_wdt->wdt_dev.info = &sunxi_wdt_info;
0267     sunxi_wdt->wdt_dev.ops = &sunxi_wdt_ops;
0268     sunxi_wdt->wdt_dev.timeout = WDT_MAX_TIMEOUT;
0269     sunxi_wdt->wdt_dev.max_timeout = WDT_MAX_TIMEOUT;
0270     sunxi_wdt->wdt_dev.min_timeout = WDT_MIN_TIMEOUT;
0271     sunxi_wdt->wdt_dev.parent = dev;
0272 
0273     watchdog_init_timeout(&sunxi_wdt->wdt_dev, timeout, dev);
0274     watchdog_set_nowayout(&sunxi_wdt->wdt_dev, nowayout);
0275     watchdog_set_restart_priority(&sunxi_wdt->wdt_dev, 128);
0276 
0277     watchdog_set_drvdata(&sunxi_wdt->wdt_dev, sunxi_wdt);
0278 
0279     sunxi_wdt_stop(&sunxi_wdt->wdt_dev);
0280 
0281     watchdog_stop_on_reboot(&sunxi_wdt->wdt_dev);
0282     err = devm_watchdog_register_device(dev, &sunxi_wdt->wdt_dev);
0283     if (unlikely(err))
0284         return err;
0285 
0286     dev_info(dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)",
0287          sunxi_wdt->wdt_dev.timeout, nowayout);
0288 
0289     return 0;
0290 }
0291 
0292 static struct platform_driver sunxi_wdt_driver = {
0293     .probe      = sunxi_wdt_probe,
0294     .driver     = {
0295         .name       = DRV_NAME,
0296         .of_match_table = sunxi_wdt_dt_ids,
0297     },
0298 };
0299 
0300 module_platform_driver(sunxi_wdt_driver);
0301 
0302 module_param(timeout, uint, 0);
0303 MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds");
0304 
0305 module_param(nowayout, bool, 0);
0306 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
0307         "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
0308 
0309 MODULE_LICENSE("GPL");
0310 MODULE_AUTHOR("Carlo Caione <carlo.caione@gmail.com>");
0311 MODULE_AUTHOR("Henrik Nordstrom <henrik@henriknordstrom.net>");
0312 MODULE_DESCRIPTION("sunxi WatchDog Timer Driver");
0313 MODULE_VERSION(DRV_VERSION);