0001
0002
0003
0004
0005
0006
0007
0008
0009
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
0042
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
0062
0063
0064
0065
0066
0067
0068 static const int wdt_timeout_map[] = {
0069 [1] = 0x1,
0070 [2] = 0x2,
0071 [3] = 0x3,
0072 [4] = 0x4,
0073 [5] = 0x5,
0074 [6] = 0x6,
0075 [8] = 0x7,
0076 [10] = 0x8,
0077 [12] = 0x9,
0078 [14] = 0xA,
0079 [16] = 0xB,
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
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
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
0107
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
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
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 { }
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);