summaryrefslogtreecommitdiff
path: root/drivers/platform/cznic/turris-omnia-mcu-watchdog.c
blob: 3ad146ec1d809b2bc62558180490e86b08ce8d15 (plain)
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
// SPDX-License-Identifier: GPL-2.0
/*
 * CZ.NIC's Turris Omnia MCU watchdog driver
 *
 * 2024 by Marek Behún <kabel@kernel.org>
 */

#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/units.h>
#include <linux/watchdog.h>

#include <linux/turris-omnia-mcu-interface.h>
#include "turris-omnia-mcu.h"

#define WATCHDOG_TIMEOUT		120

static unsigned int timeout;
module_param(timeout, int, 0);
MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds");

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 omnia_wdt_start(struct watchdog_device *wdt)
{
	struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);

	return omnia_cmd_write_u8(mcu->client, OMNIA_CMD_SET_WATCHDOG_STATE, 1);
}

static int omnia_wdt_stop(struct watchdog_device *wdt)
{
	struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);

	return omnia_cmd_write_u8(mcu->client, OMNIA_CMD_SET_WATCHDOG_STATE, 0);
}

static int omnia_wdt_ping(struct watchdog_device *wdt)
{
	struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);

	return omnia_cmd_write_u8(mcu->client, OMNIA_CMD_SET_WATCHDOG_STATE, 1);
}

static int omnia_wdt_set_timeout(struct watchdog_device *wdt,
				 unsigned int timeout)
{
	struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);

	return omnia_cmd_write_u16(mcu->client, OMNIA_CMD_SET_WDT_TIMEOUT,
				   timeout * DECI);
}

static unsigned int omnia_wdt_get_timeleft(struct watchdog_device *wdt)
{
	struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);
	u16 timeleft;
	int err;

	err = omnia_cmd_read_u16(mcu->client, OMNIA_CMD_GET_WDT_TIMELEFT,
				 &timeleft);
	if (err) {
		dev_err(&mcu->client->dev, "Cannot get watchdog timeleft: %d\n",
			err);
		return 0;
	}

	return timeleft / DECI;
}

static const struct watchdog_info omnia_wdt_info = {
	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
	.identity = "Turris Omnia MCU Watchdog",
};

static const struct watchdog_ops omnia_wdt_ops = {
	.owner		= THIS_MODULE,
	.start		= omnia_wdt_start,
	.stop		= omnia_wdt_stop,
	.ping		= omnia_wdt_ping,
	.set_timeout	= omnia_wdt_set_timeout,
	.get_timeleft	= omnia_wdt_get_timeleft,
};

int omnia_mcu_register_watchdog(struct omnia_mcu *mcu)
{
	struct device *dev = &mcu->client->dev;
	u8 state;
	int err;

	if (!(mcu->features & OMNIA_FEAT_WDT_PING))
		return 0;

	mcu->wdt.info = &omnia_wdt_info;
	mcu->wdt.ops = &omnia_wdt_ops;
	mcu->wdt.parent = dev;
	mcu->wdt.min_timeout = 1;
	mcu->wdt.max_timeout = 65535 / DECI;

	mcu->wdt.timeout = WATCHDOG_TIMEOUT;
	watchdog_init_timeout(&mcu->wdt, timeout, dev);

	watchdog_set_drvdata(&mcu->wdt, mcu);

	omnia_wdt_set_timeout(&mcu->wdt, mcu->wdt.timeout);

	err = omnia_cmd_read_u8(mcu->client, OMNIA_CMD_GET_WATCHDOG_STATE,
				&state);
	if (err)
		return dev_err_probe(dev, err,
				     "Cannot get MCU watchdog state\n");

	if (state)
		set_bit(WDOG_HW_RUNNING, &mcu->wdt.status);

	watchdog_set_nowayout(&mcu->wdt, nowayout);
	watchdog_stop_on_reboot(&mcu->wdt);
	err = devm_watchdog_register_device(dev, &mcu->wdt);
	if (err)
		return dev_err_probe(dev, err,
				     "Cannot register MCU watchdog\n");

	return 0;
}