File Index
Files and directories
Sign in
FILE

nic7018_wdt.c

File preview with syntax highlighting.

linux-6.19.14/drivers/watchdog/nic7018_wdt.c C
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2/*
  3 * Copyright (C) 2016 National Instruments Corp.
  4 */
  5
  6#include <linux/bitops.h>
  7#include <linux/device.h>
  8#include <linux/io.h>
  9#include <linux/mod_devicetable.h>
 10#include <linux/module.h>
 11#include <linux/platform_device.h>
 12#include <linux/types.h>
 13#include <linux/watchdog.h>
 14
 15#define LOCK			0xA5
 16#define UNLOCK			0x5A
 17
 18#define WDT_CTRL_RESET_EN	BIT(7)
 19#define WDT_RELOAD_PORT_EN	BIT(7)
 20
 21#define WDT_CTRL		1
 22#define WDT_RELOAD_CTRL		2
 23#define WDT_PRESET_PRESCALE	4
 24#define WDT_REG_LOCK		5
 25#define WDT_COUNT		6
 26#define WDT_RELOAD_PORT		7
 27
 28#define WDT_MIN_TIMEOUT		1
 29#define WDT_MAX_TIMEOUT		464
 30#define WDT_DEFAULT_TIMEOUT	80
 31
 32#define WDT_MAX_COUNTER		15
 33
 34static unsigned int timeout;
 35module_param(timeout, uint, 0);
 36MODULE_PARM_DESC(timeout,
 37		 "Watchdog timeout in seconds. (default="
 38		 __MODULE_STRING(WDT_DEFAULT_TIMEOUT) ")");
 39
 40static bool nowayout = WATCHDOG_NOWAYOUT;
 41module_param(nowayout, bool, 0);
 42MODULE_PARM_DESC(nowayout,
 43		 "Watchdog cannot be stopped once started. (default="
 44		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
 45
 46struct nic7018_wdt {
 47	u16 io_base;
 48	u32 period;
 49	struct watchdog_device wdd;
 50};
 51
 52struct nic7018_config {
 53	u32 period;
 54	u8 divider;
 55};
 56
 57static const struct nic7018_config nic7018_configs[] = {
 58	{  2, 4 },
 59	{ 32, 5 },
 60};
 61
 62static inline u32 nic7018_timeout(u32 period, u8 counter)
 63{
 64	return period * counter - period / 2;
 65}
 66
 67static const struct nic7018_config *nic7018_get_config(u32 timeout,
 68						       u8 *counter)
 69{
 70	const struct nic7018_config *config;
 71	u8 count;
 72
 73	if (timeout < 30 && timeout != 16) {
 74		config = &nic7018_configs[0];
 75		count = timeout / 2 + 1;
 76	} else {
 77		config = &nic7018_configs[1];
 78		count = DIV_ROUND_UP(timeout + 16, 32);
 79
 80		if (count > WDT_MAX_COUNTER)
 81			count = WDT_MAX_COUNTER;
 82	}
 83	*counter = count;
 84	return config;
 85}
 86
 87static int nic7018_set_timeout(struct watchdog_device *wdd,
 88			       unsigned int timeout)
 89{
 90	struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
 91	const struct nic7018_config *config;
 92	u8 counter;
 93
 94	config = nic7018_get_config(timeout, &counter);
 95
 96	outb(counter << 4 | config->divider,
 97	     wdt->io_base + WDT_PRESET_PRESCALE);
 98
 99	wdd->timeout = nic7018_timeout(config->period, counter);
100	wdt->period = config->period;
101
102	return 0;
103}
104
105static int nic7018_start(struct watchdog_device *wdd)
106{
107	struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
108	u8 control;
109
110	nic7018_set_timeout(wdd, wdd->timeout);
111
112	control = inb(wdt->io_base + WDT_RELOAD_CTRL);
113	outb(control | WDT_RELOAD_PORT_EN, wdt->io_base + WDT_RELOAD_CTRL);
114
115	outb(1, wdt->io_base + WDT_RELOAD_PORT);
116
117	control = inb(wdt->io_base + WDT_CTRL);
118	outb(control | WDT_CTRL_RESET_EN, wdt->io_base + WDT_CTRL);
119
120	return 0;
121}
122
123static int nic7018_stop(struct watchdog_device *wdd)
124{
125	struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
126
127	outb(0, wdt->io_base + WDT_CTRL);
128	outb(0, wdt->io_base + WDT_RELOAD_CTRL);
129	outb(0xF0, wdt->io_base + WDT_PRESET_PRESCALE);
130
131	return 0;
132}
133
134static int nic7018_ping(struct watchdog_device *wdd)
135{
136	struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
137
138	outb(1, wdt->io_base + WDT_RELOAD_PORT);
139
140	return 0;
141}
142
143static unsigned int nic7018_get_timeleft(struct watchdog_device *wdd)
144{
145	struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
146	u8 count;
147
148	count = inb(wdt->io_base + WDT_COUNT) & 0xF;
149	if (!count)
150		return 0;
151
152	return nic7018_timeout(wdt->period, count);
153}
154
155static const struct watchdog_info nic7018_wdd_info = {
156	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
157	.identity = "NIC7018 Watchdog",
158};
159
160static const struct watchdog_ops nic7018_wdd_ops = {
161	.owner = THIS_MODULE,
162	.start = nic7018_start,
163	.stop = nic7018_stop,
164	.ping = nic7018_ping,
165	.set_timeout = nic7018_set_timeout,
166	.get_timeleft = nic7018_get_timeleft,
167};
168
169static int nic7018_probe(struct platform_device *pdev)
170{
171	struct device *dev = &pdev->dev;
172	struct watchdog_device *wdd;
173	struct nic7018_wdt *wdt;
174	struct resource *io_rc;
175	int ret;
176
177	wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
178	if (!wdt)
179		return -ENOMEM;
180
181	platform_set_drvdata(pdev, wdt);
182
183	io_rc = platform_get_resource(pdev, IORESOURCE_IO, 0);
184	if (!io_rc) {
185		dev_err(dev, "missing IO resources\n");
186		return -EINVAL;
187	}
188
189	if (!devm_request_region(dev, io_rc->start, resource_size(io_rc),
190				 KBUILD_MODNAME)) {
191		dev_err(dev, "failed to get IO region\n");
192		return -EBUSY;
193	}
194
195	wdt->io_base = io_rc->start;
196	wdd = &wdt->wdd;
197	wdd->info = &nic7018_wdd_info;
198	wdd->ops = &nic7018_wdd_ops;
199	wdd->min_timeout = WDT_MIN_TIMEOUT;
200	wdd->max_timeout = WDT_MAX_TIMEOUT;
201	wdd->timeout = WDT_DEFAULT_TIMEOUT;
202	wdd->parent = dev;
203
204	watchdog_set_drvdata(wdd, wdt);
205	watchdog_set_nowayout(wdd, nowayout);
206	watchdog_init_timeout(wdd, timeout, dev);
207
208	/* Unlock WDT register */
209	outb(UNLOCK, wdt->io_base + WDT_REG_LOCK);
210
211	ret = watchdog_register_device(wdd);
212	if (ret) {
213		outb(LOCK, wdt->io_base + WDT_REG_LOCK);
214		return ret;
215	}
216
217	dev_dbg(dev, "io_base=0x%04X, timeout=%d, nowayout=%d\n",
218		wdt->io_base, timeout, nowayout);
219	return 0;
220}
221
222static void nic7018_remove(struct platform_device *pdev)
223{
224	struct nic7018_wdt *wdt = platform_get_drvdata(pdev);
225
226	watchdog_unregister_device(&wdt->wdd);
227
228	/* Lock WDT register */
229	outb(LOCK, wdt->io_base + WDT_REG_LOCK);
230}
231
232static const struct acpi_device_id nic7018_device_ids[] = {
233	{ "NIC7018" },
234	{ }
235};
236MODULE_DEVICE_TABLE(acpi, nic7018_device_ids);
237
238static struct platform_driver watchdog_driver = {
239	.probe = nic7018_probe,
240	.remove = nic7018_remove,
241	.driver = {
242		.name = KBUILD_MODNAME,
243		.acpi_match_table = nic7018_device_ids,
244	},
245};
246
247module_platform_driver(watchdog_driver);
248
249MODULE_DESCRIPTION("National Instruments NIC7018 Watchdog driver");
250MODULE_AUTHOR("Hui Chun Ong <hui.chun.ong@ni.com>");
251MODULE_LICENSE("GPL");
~/ Read-only file preview.