gxp-wdt.c 4.5 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 80 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 120 121 122 123 124 125 126 127 128 129 130 131 132 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 164 165 166 167 168 169 170 171 172 173 174
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2022 Hewlett-Packard Enterprise Development Company, L.P. */

#include <linux/delay.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <linux/watchdog.h>

#define MASK_WDGCS_ENABLE	0x01
#define MASK_WDGCS_RELOAD	0x04
#define MASK_WDGCS_NMIEN	0x08
#define MASK_WDGCS_WARN		0x80

#define WDT_MAX_TIMEOUT_MS	655350
#define WDT_DEFAULT_TIMEOUT	30
#define SECS_TO_WDOG_TICKS(x) ((x) * 100)
#define WDOG_TICKS_TO_SECS(x) ((x) / 100)

#define GXP_WDT_CNT_OFS		0x10
#define GXP_WDT_CTRL_OFS	0x16

struct gxp_wdt {
	void __iomem *base;
	struct watchdog_device wdd;
};

static void gxp_wdt_enable_reload(struct gxp_wdt *drvdata)
{
	u8 val;

	val = readb(drvdata->base + GXP_WDT_CTRL_OFS);
	val |= (MASK_WDGCS_ENABLE | MASK_WDGCS_RELOAD);
	writeb(val, drvdata->base + GXP_WDT_CTRL_OFS);
}

static int gxp_wdt_start(struct watchdog_device *wdd)
{
	struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd);

	writew(SECS_TO_WDOG_TICKS(wdd->timeout), drvdata->base + GXP_WDT_CNT_OFS);
	gxp_wdt_enable_reload(drvdata);
	return 0;
}

static int gxp_wdt_stop(struct watchdog_device *wdd)
{
	struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd);
	u8 val;

	val = readb_relaxed(drvdata->base + GXP_WDT_CTRL_OFS);
	val &= ~MASK_WDGCS_ENABLE;
	writeb(val, drvdata->base + GXP_WDT_CTRL_OFS);
	return 0;
}

static int gxp_wdt_set_timeout(struct watchdog_device *wdd,
			       unsigned int timeout)
{
	struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd);
	u32 actual;

	wdd->timeout = timeout;
	actual = min(timeout * 100, wdd->max_hw_heartbeat_ms / 10);
	writew(actual, drvdata->base + GXP_WDT_CNT_OFS);

	return 0;
}

static unsigned int gxp_wdt_get_timeleft(struct watchdog_device *wdd)
{
	struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd);
	u32 val = readw(drvdata->base + GXP_WDT_CNT_OFS);

	return WDOG_TICKS_TO_SECS(val);
}

static int gxp_wdt_ping(struct watchdog_device *wdd)
{
	struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd);

	gxp_wdt_enable_reload(drvdata);
	return 0;
}

static int gxp_restart(struct watchdog_device *wdd, unsigned long action,
		       void *data)
{
	struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd);

	writew(1, drvdata->base + GXP_WDT_CNT_OFS);
	gxp_wdt_enable_reload(drvdata);
	mdelay(100);
	return 0;
}

static const struct watchdog_ops gxp_wdt_ops = {
	.owner =	THIS_MODULE,
	.start =	gxp_wdt_start,
	.stop =		gxp_wdt_stop,
	.ping =		gxp_wdt_ping,
	.set_timeout =	gxp_wdt_set_timeout,
	.get_timeleft =	gxp_wdt_get_timeleft,
	.restart =	gxp_restart,
};

static const struct watchdog_info gxp_wdt_info = {
	.options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING,
	.identity = "HPE GXP Watchdog timer",
};

static int gxp_wdt_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct gxp_wdt *drvdata;
	int err;
	u8 val;

	drvdata = devm_kzalloc(dev, sizeof(struct gxp_wdt), GFP_KERNEL);
	if (!drvdata)
		return -ENOMEM;

	/*
	 * The register area where the timer and watchdog reside is disarranged.
	 * Hence mapping individual register blocks for the timer and watchdog
	 * is not recommended as they would have access to each others
	 * registers. Based on feedback the watchdog is no longer part of the
	 * device tree file and the timer driver now creates the watchdog as a
	 * child device. During the watchdogs creation, the timer driver passes
	 * the base address to the watchdog over the private interface.
	 */

	drvdata->base = (void __iomem *)dev->platform_data;

	drvdata->wdd.info = &gxp_wdt_info;
	drvdata->wdd.ops = &gxp_wdt_ops;
	drvdata->wdd.max_hw_heartbeat_ms = WDT_MAX_TIMEOUT_MS;
	drvdata->wdd.parent = dev;
	drvdata->wdd.timeout = WDT_DEFAULT_TIMEOUT;

	watchdog_set_drvdata(&drvdata->wdd, drvdata);
	watchdog_set_nowayout(&drvdata->wdd, WATCHDOG_NOWAYOUT);

	val = readb(drvdata->base + GXP_WDT_CTRL_OFS);

	if (val & MASK_WDGCS_ENABLE)
		set_bit(WDOG_HW_RUNNING, &drvdata->wdd.status);

	watchdog_set_restart_priority(&drvdata->wdd, 128);

	watchdog_stop_on_reboot(&drvdata->wdd);
	err = devm_watchdog_register_device(dev, &drvdata->wdd);
	if (err) {
		dev_err(dev, "Failed to register watchdog device");
		return err;
	}

	dev_info(dev, "HPE GXP watchdog timer");

	return 0;
}

static struct platform_driver gxp_wdt_driver = {
	.probe = gxp_wdt_probe,
	.driver = {
		.name =	"gxp-wdt",
	},
};
module_platform_driver(gxp_wdt_driver);

MODULE_AUTHOR("Nick Hawkins <nick.hawkins@hpe.com>");
MODULE_AUTHOR("Jean-Marie Verdun <verdun@hpe.com>");
MODULE_DESCRIPTION("Driver for GXP watchdog timer");
175
MODULE_LICENSE("GPL");