mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-24 17:59:32 +00:00
![Johannes Thumshirn](/assets/img/avatar_default.png)
When setting the timeout for the menz069_wdt watchdog driver, we erroneously read from the 'watchdog value register' (WVR) instead of the 'watchdog timer register' (WTR) and then write the value back into WTR. This can potentially lead to wrong timeouts and wrong enable bit settings. Signed-off-by: Johannes Thumshirn <jth@kernel.org> Reviewed-by: Guenter Roeck <linux@roeck-us.net> Link: https://lore.kernel.org/r/20230418172531.177349-3-jth@kernel.org Signed-off-by: Guenter Roeck <linux@roeck-us.net> Signed-off-by: Wim Van Sebroeck <wim@linux-watchdog.org>
167 lines
3.9 KiB
C
167 lines
3.9 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Watchdog driver for the MEN z069 IP-Core
|
|
*
|
|
* Copyright (C) 2018 Johannes Thumshirn <jth@kernel.org>
|
|
*/
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/mcb.h>
|
|
#include <linux/module.h>
|
|
#include <linux/watchdog.h>
|
|
|
|
struct men_z069_drv {
|
|
struct watchdog_device wdt;
|
|
void __iomem *base;
|
|
struct resource *mem;
|
|
};
|
|
|
|
#define MEN_Z069_WTR 0x10
|
|
#define MEN_Z069_WTR_WDEN BIT(15)
|
|
#define MEN_Z069_WTR_WDET_MASK 0x7fff
|
|
#define MEN_Z069_WVR 0x14
|
|
|
|
#define MEN_Z069_TIMER_FREQ 500 /* 500 Hz */
|
|
#define MEN_Z069_WDT_COUNTER_MIN 1
|
|
#define MEN_Z069_WDT_COUNTER_MAX 0x7fff
|
|
#define MEN_Z069_DEFAULT_TIMEOUT 30
|
|
|
|
static bool nowayout = WATCHDOG_NOWAYOUT;
|
|
module_param(nowayout, bool, 0);
|
|
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
|
|
__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
|
|
|
|
static int men_z069_wdt_start(struct watchdog_device *wdt)
|
|
{
|
|
struct men_z069_drv *drv = watchdog_get_drvdata(wdt);
|
|
u16 val;
|
|
|
|
val = readw(drv->base + MEN_Z069_WTR);
|
|
val |= MEN_Z069_WTR_WDEN;
|
|
writew(val, drv->base + MEN_Z069_WTR);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int men_z069_wdt_stop(struct watchdog_device *wdt)
|
|
{
|
|
struct men_z069_drv *drv = watchdog_get_drvdata(wdt);
|
|
u16 val;
|
|
|
|
val = readw(drv->base + MEN_Z069_WTR);
|
|
val &= ~MEN_Z069_WTR_WDEN;
|
|
writew(val, drv->base + MEN_Z069_WTR);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int men_z069_wdt_ping(struct watchdog_device *wdt)
|
|
{
|
|
struct men_z069_drv *drv = watchdog_get_drvdata(wdt);
|
|
u16 val;
|
|
|
|
/* The watchdog trigger value toggles between 0x5555 and 0xaaaa */
|
|
val = readw(drv->base + MEN_Z069_WVR);
|
|
val ^= 0xffff;
|
|
writew(val, drv->base + MEN_Z069_WVR);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int men_z069_wdt_set_timeout(struct watchdog_device *wdt,
|
|
unsigned int timeout)
|
|
{
|
|
struct men_z069_drv *drv = watchdog_get_drvdata(wdt);
|
|
u16 reg, val, ena;
|
|
|
|
wdt->timeout = timeout;
|
|
val = timeout * MEN_Z069_TIMER_FREQ;
|
|
|
|
reg = readw(drv->base + MEN_Z069_WTR);
|
|
ena = reg & MEN_Z069_WTR_WDEN;
|
|
reg = ena | val;
|
|
writew(reg, drv->base + MEN_Z069_WTR);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct watchdog_info men_z069_info = {
|
|
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
|
|
.identity = "MEN z069 Watchdog",
|
|
};
|
|
|
|
static const struct watchdog_ops men_z069_ops = {
|
|
.owner = THIS_MODULE,
|
|
.start = men_z069_wdt_start,
|
|
.stop = men_z069_wdt_stop,
|
|
.ping = men_z069_wdt_ping,
|
|
.set_timeout = men_z069_wdt_set_timeout,
|
|
};
|
|
|
|
static int men_z069_probe(struct mcb_device *dev,
|
|
const struct mcb_device_id *id)
|
|
{
|
|
struct men_z069_drv *drv;
|
|
struct resource *mem;
|
|
|
|
drv = devm_kzalloc(&dev->dev, sizeof(struct men_z069_drv), GFP_KERNEL);
|
|
if (!drv)
|
|
return -ENOMEM;
|
|
|
|
mem = mcb_request_mem(dev, "z069-wdt");
|
|
if (IS_ERR(mem))
|
|
return PTR_ERR(mem);
|
|
|
|
drv->base = devm_ioremap(&dev->dev, mem->start, resource_size(mem));
|
|
if (drv->base == NULL)
|
|
goto release_mem;
|
|
|
|
drv->mem = mem;
|
|
drv->wdt.info = &men_z069_info;
|
|
drv->wdt.ops = &men_z069_ops;
|
|
drv->wdt.timeout = MEN_Z069_DEFAULT_TIMEOUT;
|
|
drv->wdt.min_timeout = 1;
|
|
drv->wdt.max_timeout = MEN_Z069_WDT_COUNTER_MAX / MEN_Z069_TIMER_FREQ;
|
|
|
|
watchdog_init_timeout(&drv->wdt, 0, &dev->dev);
|
|
watchdog_set_nowayout(&drv->wdt, nowayout);
|
|
watchdog_set_drvdata(&drv->wdt, drv);
|
|
drv->wdt.parent = &dev->dev;
|
|
mcb_set_drvdata(dev, drv);
|
|
|
|
return watchdog_register_device(&drv->wdt);
|
|
|
|
release_mem:
|
|
mcb_release_mem(mem);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static void men_z069_remove(struct mcb_device *dev)
|
|
{
|
|
struct men_z069_drv *drv = mcb_get_drvdata(dev);
|
|
|
|
watchdog_unregister_device(&drv->wdt);
|
|
mcb_release_mem(drv->mem);
|
|
}
|
|
|
|
static const struct mcb_device_id men_z069_ids[] = {
|
|
{ .device = 0x45 },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(mcb, men_z069_ids);
|
|
|
|
static struct mcb_driver men_z069_driver = {
|
|
.driver = {
|
|
.name = "z069-wdt",
|
|
.owner = THIS_MODULE,
|
|
},
|
|
.probe = men_z069_probe,
|
|
.remove = men_z069_remove,
|
|
.id_table = men_z069_ids,
|
|
};
|
|
module_mcb_driver(men_z069_driver);
|
|
|
|
MODULE_AUTHOR("Johannes Thumshirn <jth@kernel.org>");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("mcb:16z069");
|
|
MODULE_IMPORT_NS(MCB);
|