1/* 2 * Moschip MCS814x Watchdog driver 3 * 4 * Copyright (C) 2012, Florian Fainelli <florian@openwrt.org> 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 */ 20 21#include <linux/module.h> 22#include <linux/slab.h> 23#include <linux/init.h> 24#include <linux/platform_device.h> 25#include <linux/miscdevice.h> 26#include <linux/watchdog.h> 27#include <linux/io.h> 28#include <linux/err.h> 29#include <linux/clk.h> 30#include <linux/of.h> 31 32#define WDT_COUNT 0x00 33#define WDT_CTRL 0x04 34#define WDT_CTRL_EN 0x1 35 36/* watchdog frequency */ 37#define WDT_MAX_VALUE (0xffffffff) 38 39struct mcs814x_wdt { 40 void __iomem *regs; 41 spinlock_t lock; 42 struct watchdog_device wdt_dev; 43 struct clk *clk; 44}; 45 46static int mcs814x_wdt_start(struct watchdog_device *dev) 47{ 48 struct mcs814x_wdt *wdt = watchdog_get_drvdata(dev); 49 u32 reg; 50 51 spin_lock(&wdt->lock); 52 reg = readl_relaxed(wdt->regs + WDT_CTRL); 53 reg |= WDT_CTRL_EN; 54 writel_relaxed(reg, wdt->regs + WDT_CTRL); 55 spin_unlock(&wdt->lock); 56 57 return 0; 58} 59 60static int mcs814x_wdt_stop(struct watchdog_device *dev) 61{ 62 struct mcs814x_wdt *wdt = watchdog_get_drvdata(dev); 63 u32 reg; 64 65 spin_lock(&wdt->lock); 66 reg = readl_relaxed(wdt->regs + WDT_CTRL); 67 reg &= ~WDT_CTRL_EN; 68 writel_relaxed(reg, wdt->regs + WDT_CTRL); 69 spin_unlock(&wdt->lock); 70 71 return 0; 72} 73 74static int mcs814x_wdt_set_timeout(struct watchdog_device *dev, 75 unsigned int new_timeout) 76{ 77 struct mcs814x_wdt *wdt = watchdog_get_drvdata(dev); 78 79 spin_lock(&wdt->lock); 80 /* watchdog counts upward and rollover (0xfffffff -> 0) 81 * triggers the reboot 82 */ 83 writel_relaxed(WDT_MAX_VALUE - (new_timeout * clk_get_rate(wdt->clk)), 84 wdt->regs + WDT_COUNT); 85 spin_unlock(&wdt->lock); 86 87 return 0; 88} 89 90static int mcs814x_wdt_ping(struct watchdog_device *dev) 91{ 92 /* restart the watchdog */ 93 mcs814x_wdt_stop(dev); 94 mcs814x_wdt_set_timeout(dev, dev->timeout); 95 mcs814x_wdt_start(dev); 96 97 return 0; 98} 99 100static const struct watchdog_info mcs814x_wdt_ident = { 101 .options = WDIOF_CARDRESET | WDIOF_SETTIMEOUT | 102 WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, 103 .identity = "MCS814x Watchdog", 104}; 105 106static struct watchdog_ops mcs814x_wdt_ops = { 107 .owner = THIS_MODULE, 108 .start = mcs814x_wdt_start, 109 .stop = mcs814x_wdt_stop, 110 .set_timeout = mcs814x_wdt_set_timeout, 111 .ping = mcs814x_wdt_ping, 112}; 113 114static int mcs814x_wdt_probe(struct platform_device *pdev) 115{ 116 struct resource *res; 117 struct mcs814x_wdt *wdt; 118 int ret; 119 struct clk *clk; 120 121 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 122 if (!res) 123 return -ENODEV; 124 125 clk = clk_get(NULL, "wdt"); 126 if (IS_ERR_OR_NULL(clk)) { 127 dev_err(&pdev->dev, "failed to get watchdog clock\n"); 128 return PTR_ERR(clk); 129 } 130 131 wdt = kzalloc(sizeof(*wdt), GFP_KERNEL); 132 if (!wdt) { 133 ret = -ENOMEM; 134 goto out_clk; 135 } 136 137 spin_lock_init(&wdt->lock); 138 wdt->clk = clk; 139 wdt->wdt_dev.info = &mcs814x_wdt_ident; 140 wdt->wdt_dev.ops = &mcs814x_wdt_ops; 141 wdt->wdt_dev.min_timeout = 1; 142 /* approximately 10995 secs */ 143 wdt->wdt_dev.max_timeout = (WDT_MAX_VALUE / clk_get_rate(clk)); 144 145 platform_set_drvdata(pdev, wdt); 146 147 /* only ioremap registers, because the register is shared */ 148 wdt->regs = devm_ioremap(&pdev->dev, res->start, resource_size(res)); 149 if (!wdt->regs) { 150 ret = -ENOMEM; 151 goto out; 152 } 153 154 watchdog_set_drvdata(&wdt->wdt_dev, wdt); 155 156 ret = watchdog_register_device(&wdt->wdt_dev); 157 if (ret) { 158 dev_err(&pdev->dev, "cannot register watchdog: %d\n", ret); 159 goto out; 160 } 161 162 dev_info(&pdev->dev, "registered\n"); 163 return 0; 164 165out: 166 platform_set_drvdata(pdev, NULL); 167 kfree(wdt); 168out_clk: 169 clk_put(clk); 170 return ret; 171} 172 173static int mcs814x_wdt_remove(struct platform_device *pdev) 174{ 175 struct mcs814x_wdt *wdt = platform_get_drvdata(pdev); 176 177 clk_put(wdt->clk); 178 watchdog_unregister_device(&wdt->wdt_dev); 179 watchdog_set_drvdata(&wdt->wdt_dev, NULL); 180 kfree(wdt); 181 platform_set_drvdata(pdev, NULL); 182 183 return 0; 184} 185 186static const struct of_device_id mcs814x_wdt_ids[] = { 187 { .compatible = "moschip,mcs814x-wdt", }, 188 { /* sentinel */ }, 189}; 190 191static struct platform_driver mcs814x_wdt_driver = { 192 .driver = { 193 .name = "mcs814x-wdt", 194 .owner = THIS_MODULE, 195 .of_match_table = mcs814x_wdt_ids, 196 }, 197 .probe = mcs814x_wdt_probe, 198 .remove = mcs814x_wdt_remove, 199}; 200 201module_platform_driver(mcs814x_wdt_driver); 202 203MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>"); 204MODULE_DESCRIPTION("Moschip MCS814x Watchdog driver"); 205MODULE_LICENSE("GPL"); 206MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 207MODULE_ALIAS("platform:mcs814x-wdt"); 208