166aaa7a5SMarc Zyngier /* 266aaa7a5SMarc Zyngier * drivers/char/watchdog/max63xx_wdt.c 366aaa7a5SMarc Zyngier * 466aaa7a5SMarc Zyngier * Driver for max63{69,70,71,72,73,74} watchdog timers 566aaa7a5SMarc Zyngier * 666aaa7a5SMarc Zyngier * Copyright (C) 2009 Marc Zyngier <maz@misterjones.org> 766aaa7a5SMarc Zyngier * 866aaa7a5SMarc Zyngier * This file is licensed under the terms of the GNU General Public 966aaa7a5SMarc Zyngier * License version 2. This program is licensed "as is" without any 1066aaa7a5SMarc Zyngier * warranty of any kind, whether express or implied. 1166aaa7a5SMarc Zyngier * 1266aaa7a5SMarc Zyngier * This driver assumes the watchdog pins are memory mapped (as it is 1366aaa7a5SMarc Zyngier * the case for the Arcom Zeus). Should it be connected over GPIOs or 1466aaa7a5SMarc Zyngier * another interface, some abstraction will have to be introduced. 1566aaa7a5SMarc Zyngier */ 1666aaa7a5SMarc Zyngier 174c271bb6SThierry Reding #include <linux/err.h> 1866aaa7a5SMarc Zyngier #include <linux/module.h> 1966aaa7a5SMarc Zyngier #include <linux/moduleparam.h> 20ac316725SRandy Dunlap #include <linux/mod_devicetable.h> 2166aaa7a5SMarc Zyngier #include <linux/types.h> 2266aaa7a5SMarc Zyngier #include <linux/kernel.h> 2366aaa7a5SMarc Zyngier #include <linux/watchdog.h> 2466aaa7a5SMarc Zyngier #include <linux/bitops.h> 2566aaa7a5SMarc Zyngier #include <linux/platform_device.h> 2666aaa7a5SMarc Zyngier #include <linux/spinlock.h> 2766aaa7a5SMarc Zyngier #include <linux/io.h> 285a0e3ad6STejun Heo #include <linux/slab.h> 29585ba602SLinus Walleij #include <linux/property.h> 3066aaa7a5SMarc Zyngier 3166aaa7a5SMarc Zyngier #define DEFAULT_HEARTBEAT 60 3266aaa7a5SMarc Zyngier #define MAX_HEARTBEAT 60 3366aaa7a5SMarc Zyngier 34a0f36833SAxel Lin static unsigned int heartbeat = DEFAULT_HEARTBEAT; 3586a1e189SWim Van Sebroeck static bool nowayout = WATCHDOG_NOWAYOUT; 3666aaa7a5SMarc Zyngier 3766aaa7a5SMarc Zyngier /* 3866aaa7a5SMarc Zyngier * Memory mapping: a single byte, 3 first lower bits to select bit 3 3966aaa7a5SMarc Zyngier * to ping the watchdog. 4066aaa7a5SMarc Zyngier */ 4166aaa7a5SMarc Zyngier #define MAX6369_WDSET (7 << 0) 4266aaa7a5SMarc Zyngier #define MAX6369_WDI (1 << 3) 4366aaa7a5SMarc Zyngier 44b9be9660SVivien Didelot #define MAX6369_WDSET_DISABLED 3 4566aaa7a5SMarc Zyngier 4666aaa7a5SMarc Zyngier static int nodelay; 47b9be9660SVivien Didelot 48b9be9660SVivien Didelot struct max63xx_wdt { 49b9be9660SVivien Didelot struct watchdog_device wdd; 50b9be9660SVivien Didelot const struct max63xx_timeout *timeout; 51b9be9660SVivien Didelot 52b9be9660SVivien Didelot /* memory mapping */ 53b9be9660SVivien Didelot void __iomem *base; 54b9be9660SVivien Didelot spinlock_t lock; 55b9be9660SVivien Didelot 56b9be9660SVivien Didelot /* WDI and WSET bits write access routines */ 57b9be9660SVivien Didelot void (*ping)(struct max63xx_wdt *wdt); 58b9be9660SVivien Didelot void (*set)(struct max63xx_wdt *wdt, u8 set); 59b9be9660SVivien Didelot }; 6066aaa7a5SMarc Zyngier 6166aaa7a5SMarc Zyngier /* 6266aaa7a5SMarc Zyngier * The timeout values used are actually the absolute minimum the chip 6366aaa7a5SMarc Zyngier * offers. Typical values on my board are slightly over twice as long 6466aaa7a5SMarc Zyngier * (10s setting ends up with a 25s timeout), and can be up to 3 times 6566aaa7a5SMarc Zyngier * the nominal setting (according to the datasheet). So please take 6666aaa7a5SMarc Zyngier * these values with a grain of salt. Same goes for the initial delay 6766aaa7a5SMarc Zyngier * "feature". Only max6373/74 have a few settings without this initial 6866aaa7a5SMarc Zyngier * delay (selected with the "nodelay" parameter). 6966aaa7a5SMarc Zyngier * 7066aaa7a5SMarc Zyngier * I also decided to remove from the tables any timeout smaller than a 7166aaa7a5SMarc Zyngier * second, as it looked completly overkill... 7266aaa7a5SMarc Zyngier */ 7366aaa7a5SMarc Zyngier 7466aaa7a5SMarc Zyngier /* Timeouts in second */ 7566aaa7a5SMarc Zyngier struct max63xx_timeout { 76b9be9660SVivien Didelot const u8 wdset; 77b9be9660SVivien Didelot const u8 tdelay; 78b9be9660SVivien Didelot const u8 twd; 7966aaa7a5SMarc Zyngier }; 8066aaa7a5SMarc Zyngier 81b9be9660SVivien Didelot static const struct max63xx_timeout max6369_table[] = { 8266aaa7a5SMarc Zyngier { 5, 1, 1 }, 8366aaa7a5SMarc Zyngier { 6, 10, 10 }, 8466aaa7a5SMarc Zyngier { 7, 60, 60 }, 8566aaa7a5SMarc Zyngier { }, 8666aaa7a5SMarc Zyngier }; 8766aaa7a5SMarc Zyngier 88b9be9660SVivien Didelot static const struct max63xx_timeout max6371_table[] = { 8966aaa7a5SMarc Zyngier { 6, 60, 3 }, 9066aaa7a5SMarc Zyngier { 7, 60, 60 }, 9166aaa7a5SMarc Zyngier { }, 9266aaa7a5SMarc Zyngier }; 9366aaa7a5SMarc Zyngier 94b9be9660SVivien Didelot static const struct max63xx_timeout max6373_table[] = { 9566aaa7a5SMarc Zyngier { 2, 60, 1 }, 9666aaa7a5SMarc Zyngier { 5, 0, 1 }, 9766aaa7a5SMarc Zyngier { 1, 3, 3 }, 9866aaa7a5SMarc Zyngier { 7, 60, 10 }, 9966aaa7a5SMarc Zyngier { 6, 0, 10 }, 10066aaa7a5SMarc Zyngier { }, 10166aaa7a5SMarc Zyngier }; 10266aaa7a5SMarc Zyngier 103585ba602SLinus Walleij static const struct max63xx_timeout * 104585ba602SLinus Walleij max63xx_select_timeout(const struct max63xx_timeout *table, int value) 10566aaa7a5SMarc Zyngier { 10666aaa7a5SMarc Zyngier while (table->twd) { 10766aaa7a5SMarc Zyngier if (value <= table->twd) { 10866aaa7a5SMarc Zyngier if (nodelay && table->tdelay == 0) 10966aaa7a5SMarc Zyngier return table; 11066aaa7a5SMarc Zyngier 11166aaa7a5SMarc Zyngier if (!nodelay) 11266aaa7a5SMarc Zyngier return table; 11366aaa7a5SMarc Zyngier } 11466aaa7a5SMarc Zyngier 11566aaa7a5SMarc Zyngier table++; 11666aaa7a5SMarc Zyngier } 11766aaa7a5SMarc Zyngier 11866aaa7a5SMarc Zyngier return NULL; 11966aaa7a5SMarc Zyngier } 12066aaa7a5SMarc Zyngier 121a0f36833SAxel Lin static int max63xx_wdt_ping(struct watchdog_device *wdd) 12266aaa7a5SMarc Zyngier { 123b9be9660SVivien Didelot struct max63xx_wdt *wdt = watchdog_get_drvdata(wdd); 12466aaa7a5SMarc Zyngier 125b9be9660SVivien Didelot wdt->ping(wdt); 126a0f36833SAxel Lin return 0; 12766aaa7a5SMarc Zyngier } 12866aaa7a5SMarc Zyngier 129a0f36833SAxel Lin static int max63xx_wdt_start(struct watchdog_device *wdd) 13066aaa7a5SMarc Zyngier { 131b9be9660SVivien Didelot struct max63xx_wdt *wdt = watchdog_get_drvdata(wdd); 13266aaa7a5SMarc Zyngier 133b9be9660SVivien Didelot wdt->set(wdt, wdt->timeout->wdset); 13466aaa7a5SMarc Zyngier 13566aaa7a5SMarc Zyngier /* check for a edge triggered startup */ 136b9be9660SVivien Didelot if (wdt->timeout->tdelay == 0) 137b9be9660SVivien Didelot wdt->ping(wdt); 138a0f36833SAxel Lin return 0; 13966aaa7a5SMarc Zyngier } 14066aaa7a5SMarc Zyngier 141a0f36833SAxel Lin static int max63xx_wdt_stop(struct watchdog_device *wdd) 14266aaa7a5SMarc Zyngier { 143b9be9660SVivien Didelot struct max63xx_wdt *wdt = watchdog_get_drvdata(wdd); 144b1183e06SMarc Zyngier 145b9be9660SVivien Didelot wdt->set(wdt, MAX6369_WDSET_DISABLED); 14666aaa7a5SMarc Zyngier return 0; 14766aaa7a5SMarc Zyngier } 14866aaa7a5SMarc Zyngier 149a0f36833SAxel Lin static const struct watchdog_ops max63xx_wdt_ops = { 150a0f36833SAxel Lin .owner = THIS_MODULE, 151a0f36833SAxel Lin .start = max63xx_wdt_start, 152a0f36833SAxel Lin .stop = max63xx_wdt_stop, 153a0f36833SAxel Lin .ping = max63xx_wdt_ping, 154a0f36833SAxel Lin }; 155a0f36833SAxel Lin 156b9be9660SVivien Didelot static const struct watchdog_info max63xx_wdt_info = { 157b9be9660SVivien Didelot .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 158b9be9660SVivien Didelot .identity = "max63xx Watchdog", 15966aaa7a5SMarc Zyngier }; 16066aaa7a5SMarc Zyngier 161b9be9660SVivien Didelot static void max63xx_mmap_ping(struct max63xx_wdt *wdt) 162b9be9660SVivien Didelot { 163b9be9660SVivien Didelot u8 val; 164b9be9660SVivien Didelot 165b9be9660SVivien Didelot spin_lock(&wdt->lock); 166b9be9660SVivien Didelot 167b9be9660SVivien Didelot val = __raw_readb(wdt->base); 168b9be9660SVivien Didelot 169b9be9660SVivien Didelot __raw_writeb(val | MAX6369_WDI, wdt->base); 170b9be9660SVivien Didelot __raw_writeb(val & ~MAX6369_WDI, wdt->base); 171b9be9660SVivien Didelot 172b9be9660SVivien Didelot spin_unlock(&wdt->lock); 173b9be9660SVivien Didelot } 174b9be9660SVivien Didelot 175b9be9660SVivien Didelot static void max63xx_mmap_set(struct max63xx_wdt *wdt, u8 set) 176b9be9660SVivien Didelot { 177b9be9660SVivien Didelot u8 val; 178b9be9660SVivien Didelot 179b9be9660SVivien Didelot spin_lock(&wdt->lock); 180b9be9660SVivien Didelot 181b9be9660SVivien Didelot val = __raw_readb(wdt->base); 182b9be9660SVivien Didelot val &= ~MAX6369_WDSET; 183b9be9660SVivien Didelot val |= set & MAX6369_WDSET; 184b9be9660SVivien Didelot __raw_writeb(val, wdt->base); 185b9be9660SVivien Didelot 186b9be9660SVivien Didelot spin_unlock(&wdt->lock); 187b9be9660SVivien Didelot } 188b9be9660SVivien Didelot 189b9be9660SVivien Didelot static int max63xx_mmap_init(struct platform_device *p, struct max63xx_wdt *wdt) 190b9be9660SVivien Didelot { 1910f0a6a28SGuenter Roeck wdt->base = devm_platform_ioremap_resource(p, 0); 192b9be9660SVivien Didelot if (IS_ERR(wdt->base)) 193b9be9660SVivien Didelot return PTR_ERR(wdt->base); 194b9be9660SVivien Didelot 195b9be9660SVivien Didelot spin_lock_init(&wdt->lock); 196b9be9660SVivien Didelot 197b9be9660SVivien Didelot wdt->ping = max63xx_mmap_ping; 198b9be9660SVivien Didelot wdt->set = max63xx_mmap_set; 199b9be9660SVivien Didelot return 0; 200b9be9660SVivien Didelot } 201b9be9660SVivien Didelot 2022d991a16SBill Pemberton static int max63xx_wdt_probe(struct platform_device *pdev) 20366aaa7a5SMarc Zyngier { 20480cb6bddSGuenter Roeck struct device *dev = &pdev->dev; 205b9be9660SVivien Didelot struct max63xx_wdt *wdt; 206585ba602SLinus Walleij const struct max63xx_timeout *table; 207b9be9660SVivien Didelot int err; 208b9be9660SVivien Didelot 20980cb6bddSGuenter Roeck wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); 210b9be9660SVivien Didelot if (!wdt) 211b9be9660SVivien Didelot return -ENOMEM; 21266aaa7a5SMarc Zyngier 213585ba602SLinus Walleij /* Attempt to use fwnode first */ 214585ba602SLinus Walleij table = device_get_match_data(dev); 215585ba602SLinus Walleij if (!table) 21666aaa7a5SMarc Zyngier table = (struct max63xx_timeout *)pdev->id_entry->driver_data; 21766aaa7a5SMarc Zyngier 21866aaa7a5SMarc Zyngier if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) 21966aaa7a5SMarc Zyngier heartbeat = DEFAULT_HEARTBEAT; 22066aaa7a5SMarc Zyngier 221b9be9660SVivien Didelot wdt->timeout = max63xx_select_timeout(table, heartbeat); 222b9be9660SVivien Didelot if (!wdt->timeout) { 22380cb6bddSGuenter Roeck dev_err(dev, "unable to satisfy %ds heartbeat request\n", 224b9be9660SVivien Didelot heartbeat); 22566aaa7a5SMarc Zyngier return -EINVAL; 22666aaa7a5SMarc Zyngier } 22766aaa7a5SMarc Zyngier 228b9be9660SVivien Didelot err = max63xx_mmap_init(pdev, wdt); 229b9be9660SVivien Didelot if (err) 230b9be9660SVivien Didelot return err; 231b9be9660SVivien Didelot 232b9be9660SVivien Didelot platform_set_drvdata(pdev, &wdt->wdd); 233b9be9660SVivien Didelot watchdog_set_drvdata(&wdt->wdd, wdt); 234b9be9660SVivien Didelot 23580cb6bddSGuenter Roeck wdt->wdd.parent = dev; 236b9be9660SVivien Didelot wdt->wdd.timeout = wdt->timeout->twd; 237b9be9660SVivien Didelot wdt->wdd.info = &max63xx_wdt_info; 238b9be9660SVivien Didelot wdt->wdd.ops = &max63xx_wdt_ops; 239b9be9660SVivien Didelot 240b9be9660SVivien Didelot watchdog_set_nowayout(&wdt->wdd, nowayout); 241b9be9660SVivien Didelot 24280cb6bddSGuenter Roeck err = devm_watchdog_register_device(dev, &wdt->wdd); 243b9be9660SVivien Didelot if (err) 244b9be9660SVivien Didelot return err; 245b9be9660SVivien Didelot 24680cb6bddSGuenter Roeck dev_info(dev, "using %ds heartbeat with %ds initial delay\n", 247b9be9660SVivien Didelot wdt->timeout->twd, wdt->timeout->tdelay); 248b9be9660SVivien Didelot return 0; 24966aaa7a5SMarc Zyngier } 25066aaa7a5SMarc Zyngier 2518c7c72c9SKrzysztof Kozlowski static const struct platform_device_id max63xx_id_table[] = { 25266aaa7a5SMarc Zyngier { "max6369_wdt", (kernel_ulong_t)max6369_table, }, 25366aaa7a5SMarc Zyngier { "max6370_wdt", (kernel_ulong_t)max6369_table, }, 25466aaa7a5SMarc Zyngier { "max6371_wdt", (kernel_ulong_t)max6371_table, }, 25566aaa7a5SMarc Zyngier { "max6372_wdt", (kernel_ulong_t)max6371_table, }, 25666aaa7a5SMarc Zyngier { "max6373_wdt", (kernel_ulong_t)max6373_table, }, 25766aaa7a5SMarc Zyngier { "max6374_wdt", (kernel_ulong_t)max6373_table, }, 25866aaa7a5SMarc Zyngier { }, 25966aaa7a5SMarc Zyngier }; 26066aaa7a5SMarc Zyngier MODULE_DEVICE_TABLE(platform, max63xx_id_table); 26166aaa7a5SMarc Zyngier 262585ba602SLinus Walleij static const struct of_device_id max63xx_dt_id_table[] = { 263585ba602SLinus Walleij { .compatible = "maxim,max6369", .data = max6369_table, }, 264585ba602SLinus Walleij { .compatible = "maxim,max6370", .data = max6369_table, }, 265585ba602SLinus Walleij { .compatible = "maxim,max6371", .data = max6371_table, }, 266585ba602SLinus Walleij { .compatible = "maxim,max6372", .data = max6371_table, }, 267585ba602SLinus Walleij { .compatible = "maxim,max6373", .data = max6373_table, }, 268585ba602SLinus Walleij { .compatible = "maxim,max6374", .data = max6373_table, }, 269585ba602SLinus Walleij { } 270585ba602SLinus Walleij }; 271585ba602SLinus Walleij MODULE_DEVICE_TABLE(of, max63xx_dt_id_table); 272585ba602SLinus Walleij 27366aaa7a5SMarc Zyngier static struct platform_driver max63xx_wdt_driver = { 27466aaa7a5SMarc Zyngier .probe = max63xx_wdt_probe, 27566aaa7a5SMarc Zyngier .id_table = max63xx_id_table, 27666aaa7a5SMarc Zyngier .driver = { 27766aaa7a5SMarc Zyngier .name = "max63xx_wdt", 278585ba602SLinus Walleij .of_match_table = max63xx_dt_id_table, 27966aaa7a5SMarc Zyngier }, 28066aaa7a5SMarc Zyngier }; 28166aaa7a5SMarc Zyngier 282b8ec6118SAxel Lin module_platform_driver(max63xx_wdt_driver); 28366aaa7a5SMarc Zyngier 28466aaa7a5SMarc Zyngier MODULE_AUTHOR("Marc Zyngier <maz@misterjones.org>"); 28566aaa7a5SMarc Zyngier MODULE_DESCRIPTION("max63xx Watchdog Driver"); 28666aaa7a5SMarc Zyngier 28766aaa7a5SMarc Zyngier module_param(heartbeat, int, 0); 28866aaa7a5SMarc Zyngier MODULE_PARM_DESC(heartbeat, 28966aaa7a5SMarc Zyngier "Watchdog heartbeat period in seconds from 1 to " 29066aaa7a5SMarc Zyngier __MODULE_STRING(MAX_HEARTBEAT) ", default " 29166aaa7a5SMarc Zyngier __MODULE_STRING(DEFAULT_HEARTBEAT)); 29266aaa7a5SMarc Zyngier 29386a1e189SWim Van Sebroeck module_param(nowayout, bool, 0); 29466aaa7a5SMarc Zyngier MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 29566aaa7a5SMarc Zyngier __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 29666aaa7a5SMarc Zyngier 29766aaa7a5SMarc Zyngier module_param(nodelay, int, 0); 29866aaa7a5SMarc Zyngier MODULE_PARM_DESC(nodelay, 29966aaa7a5SMarc Zyngier "Force selection of a timeout setting without initial delay " 30066aaa7a5SMarc Zyngier "(max6373/74 only, default=0)"); 30166aaa7a5SMarc Zyngier 30229efefb9SUwe Kleine-König MODULE_LICENSE("GPL v2"); 303