1256694Snp// SPDX-License-Identifier: GPL-2.0-or-later 2256694Snp/* 3256694Snp * Copyright (C) 2016 National Instruments Corp. 4256694Snp */ 5256694Snp 6256694Snp#include <linux/acpi.h> 7256694Snp#include <linux/bitops.h> 8256694Snp#include <linux/device.h> 9256694Snp#include <linux/io.h> 10256694Snp#include <linux/module.h> 11256694Snp#include <linux/platform_device.h> 12256694Snp#include <linux/watchdog.h> 13256694Snp 14256694Snp#define LOCK 0xA5 15256694Snp#define UNLOCK 0x5A 16256694Snp 17256694Snp#define WDT_CTRL_RESET_EN BIT(7) 18256694Snp#define WDT_RELOAD_PORT_EN BIT(7) 19256694Snp 20256694Snp#define WDT_CTRL 1 21256694Snp#define WDT_RELOAD_CTRL 2 22256694Snp#define WDT_PRESET_PRESCALE 4 23256694Snp#define WDT_REG_LOCK 5 24256694Snp#define WDT_COUNT 6 25256694Snp#define WDT_RELOAD_PORT 7 26256694Snp 27256694Snp#define WDT_MIN_TIMEOUT 1 28256694Snp#define WDT_MAX_TIMEOUT 464 29256694Snp#define WDT_DEFAULT_TIMEOUT 80 30256694Snp 31256694Snp#define WDT_MAX_COUNTER 15 32256694Snp 33256694Snpstatic unsigned int timeout; 34256694Snpmodule_param(timeout, uint, 0); 35256694SnpMODULE_PARM_DESC(timeout, 36256694Snp "Watchdog timeout in seconds. (default=" 37256694Snp __MODULE_STRING(WDT_DEFAULT_TIMEOUT) ")"); 38256694Snp 39256694Snpstatic bool nowayout = WATCHDOG_NOWAYOUT; 40256694Snpmodule_param(nowayout, bool, 0); 41256694SnpMODULE_PARM_DESC(nowayout, 42256694Snp "Watchdog cannot be stopped once started. (default=" 43256694Snp __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 44256694Snp 45256694Snpstruct nic7018_wdt { 46256694Snp u16 io_base; 47256694Snp u32 period; 48256694Snp struct watchdog_device wdd; 49256694Snp}; 50256694Snp 51256694Snpstruct nic7018_config { 52256694Snp u32 period; 53256694Snp u8 divider; 54256694Snp}; 55256694Snp 56256694Snpstatic const struct nic7018_config nic7018_configs[] = { 57256694Snp { 2, 4 }, 58256694Snp { 32, 5 }, 59256694Snp}; 60256694Snp 61256694Snpstatic inline u32 nic7018_timeout(u32 period, u8 counter) 62256694Snp{ 63256694Snp return period * counter - period / 2; 64256694Snp} 65256694Snp 66256694Snpstatic const struct nic7018_config *nic7018_get_config(u32 timeout, 67256694Snp u8 *counter) 68256694Snp{ 69256694Snp const struct nic7018_config *config; 70256694Snp u8 count; 71256694Snp 72256694Snp if (timeout < 30 && timeout != 16) { 73256694Snp config = &nic7018_configs[0]; 74256694Snp count = timeout / 2 + 1; 75256694Snp } else { 76256694Snp config = &nic7018_configs[1]; 77256694Snp count = DIV_ROUND_UP(timeout + 16, 32); 78256694Snp 79256694Snp if (count > WDT_MAX_COUNTER) 80256694Snp count = WDT_MAX_COUNTER; 81256694Snp } 82256694Snp *counter = count; 83256694Snp return config; 84256694Snp} 85256694Snp 86256694Snpstatic int nic7018_set_timeout(struct watchdog_device *wdd, 87256694Snp unsigned int timeout) 88256694Snp{ 89256694Snp struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd); 90256694Snp const struct nic7018_config *config; 91256694Snp u8 counter; 92256694Snp 93256694Snp config = nic7018_get_config(timeout, &counter); 94256694Snp 95256694Snp outb(counter << 4 | config->divider, 96256694Snp wdt->io_base + WDT_PRESET_PRESCALE); 97256694Snp 98256694Snp wdd->timeout = nic7018_timeout(config->period, counter); 99256694Snp wdt->period = config->period; 100256694Snp 101256694Snp return 0; 102256694Snp} 103256694Snp 104256694Snpstatic int nic7018_start(struct watchdog_device *wdd) 105256694Snp{ 106256694Snp struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd); 107256694Snp u8 control; 108256694Snp 109256694Snp nic7018_set_timeout(wdd, wdd->timeout); 110256694Snp 111256694Snp control = inb(wdt->io_base + WDT_RELOAD_CTRL); 112256694Snp outb(control | WDT_RELOAD_PORT_EN, wdt->io_base + WDT_RELOAD_CTRL); 113256694Snp 114256694Snp outb(1, wdt->io_base + WDT_RELOAD_PORT); 115256694Snp 116256694Snp control = inb(wdt->io_base + WDT_CTRL); 117256694Snp outb(control | WDT_CTRL_RESET_EN, wdt->io_base + WDT_CTRL); 118256694Snp 119256694Snp return 0; 120256694Snp} 121256694Snp 122256694Snpstatic int nic7018_stop(struct watchdog_device *wdd) 123256694Snp{ 124256694Snp struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd); 125256694Snp 126256694Snp outb(0, wdt->io_base + WDT_CTRL); 127256694Snp outb(0, wdt->io_base + WDT_RELOAD_CTRL); 128256694Snp outb(0xF0, wdt->io_base + WDT_PRESET_PRESCALE); 129256694Snp 130256694Snp return 0; 131256694Snp} 132256694Snp 133256694Snpstatic int nic7018_ping(struct watchdog_device *wdd) 134256694Snp{ 135256694Snp struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd); 136256694Snp 137256694Snp outb(1, wdt->io_base + WDT_RELOAD_PORT); 138256694Snp 139256694Snp return 0; 140256694Snp} 141256694Snp 142256694Snpstatic unsigned int nic7018_get_timeleft(struct watchdog_device *wdd) 143256694Snp{ 144256694Snp struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd); 145256694Snp u8 count; 146256694Snp 147256694Snp count = inb(wdt->io_base + WDT_COUNT) & 0xF; 148256694Snp if (!count) 149256694Snp return 0; 150256694Snp 151256694Snp return nic7018_timeout(wdt->period, count); 152256694Snp} 153256694Snp 154256694Snpstatic const struct watchdog_info nic7018_wdd_info = { 155256694Snp .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 156256694Snp .identity = "NIC7018 Watchdog", 157256694Snp}; 158256694Snp 159256694Snpstatic const struct watchdog_ops nic7018_wdd_ops = { 160256694Snp .owner = THIS_MODULE, 161256694Snp .start = nic7018_start, 162256694Snp .stop = nic7018_stop, 163256694Snp .ping = nic7018_ping, 164256694Snp .set_timeout = nic7018_set_timeout, 165256694Snp .get_timeleft = nic7018_get_timeleft, 166256694Snp}; 167256694Snp 168256694Snpstatic int nic7018_probe(struct platform_device *pdev) 169256694Snp{ 170256694Snp struct device *dev = &pdev->dev; 171256694Snp struct watchdog_device *wdd; 172256694Snp struct nic7018_wdt *wdt; 173256694Snp struct resource *io_rc; 174256694Snp int ret; 175256694Snp 176256694Snp wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); 177256694Snp if (!wdt) 178256694Snp return -ENOMEM; 179256694Snp 180256694Snp platform_set_drvdata(pdev, wdt); 181256694Snp 182256694Snp io_rc = platform_get_resource(pdev, IORESOURCE_IO, 0); 183256694Snp if (!io_rc) { 184256694Snp dev_err(dev, "missing IO resources\n"); 185256694Snp return -EINVAL; 186256694Snp } 187256694Snp 188256694Snp if (!devm_request_region(dev, io_rc->start, resource_size(io_rc), 189256694Snp KBUILD_MODNAME)) { 190256694Snp dev_err(dev, "failed to get IO region\n"); 191256694Snp return -EBUSY; 192256694Snp } 193256694Snp 194256694Snp wdt->io_base = io_rc->start; 195256694Snp wdd = &wdt->wdd; 196256694Snp wdd->info = &nic7018_wdd_info; 197256694Snp wdd->ops = &nic7018_wdd_ops; 198256694Snp wdd->min_timeout = WDT_MIN_TIMEOUT; 199256694Snp wdd->max_timeout = WDT_MAX_TIMEOUT; 200256694Snp wdd->timeout = WDT_DEFAULT_TIMEOUT; 201256694Snp wdd->parent = dev; 202256694Snp 203256694Snp watchdog_set_drvdata(wdd, wdt); 204256694Snp watchdog_set_nowayout(wdd, nowayout); 205256694Snp watchdog_init_timeout(wdd, timeout, dev); 206256694Snp 207256694Snp /* Unlock WDT register */ 208256694Snp outb(UNLOCK, wdt->io_base + WDT_REG_LOCK); 209256694Snp 210256694Snp ret = watchdog_register_device(wdd); 211256694Snp if (ret) { 212256694Snp outb(LOCK, wdt->io_base + WDT_REG_LOCK); 213256694Snp return ret; 214256694Snp } 215256694Snp 216256694Snp dev_dbg(dev, "io_base=0x%04X, timeout=%d, nowayout=%d\n", 217256694Snp wdt->io_base, timeout, nowayout); 218256694Snp return 0; 219256694Snp} 220256694Snp 221256694Snpstatic void nic7018_remove(struct platform_device *pdev) 222256694Snp{ 223256694Snp struct nic7018_wdt *wdt = platform_get_drvdata(pdev); 224256694Snp 225256694Snp watchdog_unregister_device(&wdt->wdd); 226256694Snp 227256694Snp /* Lock WDT register */ 228256694Snp outb(LOCK, wdt->io_base + WDT_REG_LOCK); 229256694Snp} 230256694Snp 231256694Snpstatic const struct acpi_device_id nic7018_device_ids[] = { 232256694Snp {"NIC7018", 0}, 233256694Snp {"", 0}, 234256694Snp}; 235256694SnpMODULE_DEVICE_TABLE(acpi, nic7018_device_ids); 236256694Snp 237256694Snpstatic struct platform_driver watchdog_driver = { 238256694Snp .probe = nic7018_probe, 239256694Snp .remove_new = nic7018_remove, 240256694Snp .driver = { 241256694Snp .name = KBUILD_MODNAME, 242256694Snp .acpi_match_table = ACPI_PTR(nic7018_device_ids), 243256694Snp }, 244256694Snp}; 245256694Snp 246256694Snpmodule_platform_driver(watchdog_driver); 247256694Snp 248256694SnpMODULE_DESCRIPTION("National Instruments NIC7018 Watchdog driver"); 249256694SnpMODULE_AUTHOR("Hui Chun Ong <hui.chun.ong@ni.com>"); 250256694SnpMODULE_LICENSE("GPL"); 251256694Snp