menz69_wdt.c 3.98 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
// 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;

80
	reg = readw(drv->base + MEN_Z069_WTR);
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
	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;
120 121 122 123 124
	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;
125 126 127 128 129 130 131

	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);

132
	return watchdog_register_device(&drv->wdt);
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163

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",
	},
	.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>");
164
MODULE_DESCRIPTION("Watchdog driver for the MEN z069 IP-Core");
165 166
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("mcb:16z069");
167
MODULE_IMPORT_NS(MCB);