1 From 33ae4e4c86bc6ff298489fb8b743e2743dd0af6d Mon Sep 17 00:00:00 2001
2 From: =?UTF-8?q?Marek=20Beh=C3=BAn?= <kabel@kernel.org>
3 Date: Mon, 1 Jul 2024 13:30:07 +0200
4 Subject: [PATCH 05/11] platform: cznic: turris-omnia-mcu: Add support for MCU
7 Content-Type: text/plain; charset=UTF-8
8 Content-Transfer-Encoding: 8bit
10 Add support for the watchdog mechanism provided by the MCU.
12 Signed-off-by: Marek BehĂșn <kabel@kernel.org>
13 Reviewed-by: Andy Shevchenko <andy@kernel.org>
14 Reviewed-by: Guenter Roeck <linux@roeck-us.net>
15 Acked-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
16 Link: https://lore.kernel.org/r/20240701113010.16447-6-kabel@kernel.org
17 Signed-off-by: Arnd Bergmann <arnd@arndb.de>
19 drivers/platform/cznic/Kconfig | 2 +
20 drivers/platform/cznic/Makefile | 1 +
21 .../platform/cznic/turris-omnia-mcu-base.c | 4 +
22 .../cznic/turris-omnia-mcu-watchdog.c | 130 ++++++++++++++++++
23 drivers/platform/cznic/turris-omnia-mcu.h | 24 ++++
24 5 files changed, 161 insertions(+)
25 create mode 100644 drivers/platform/cznic/turris-omnia-mcu-watchdog.c
27 --- a/drivers/platform/cznic/Kconfig
28 +++ b/drivers/platform/cznic/Kconfig
29 @@ -19,6 +19,7 @@ config TURRIS_OMNIA_MCU
31 select GPIOLIB_IRQCHIP
33 + select WATCHDOG_CORE
35 Say Y here to add support for the features implemented by the
36 microcontroller on the CZ.NIC's Turris Omnia SOHO router.
37 @@ -26,6 +27,7 @@ config TURRIS_OMNIA_MCU
38 - board poweroff into true low power mode (with voltage regulators
39 disabled) and the ability to configure wake up from this mode (via
43 - to get front button press events (the front button can be
44 configured either to generate press events to the CPU or to change
45 --- a/drivers/platform/cznic/Makefile
46 +++ b/drivers/platform/cznic/Makefile
47 @@ -4,3 +4,4 @@ obj-$(CONFIG_TURRIS_OMNIA_MCU) += turris
48 turris-omnia-mcu-y := turris-omnia-mcu-base.o
49 turris-omnia-mcu-y += turris-omnia-mcu-gpio.o
50 turris-omnia-mcu-y += turris-omnia-mcu-sys-off-wakeup.o
51 +turris-omnia-mcu-y += turris-omnia-mcu-watchdog.o
52 --- a/drivers/platform/cznic/turris-omnia-mcu-base.c
53 +++ b/drivers/platform/cznic/turris-omnia-mcu-base.c
54 @@ -376,6 +376,10 @@ static int omnia_mcu_probe(struct i2c_cl
58 + err = omnia_mcu_register_watchdog(mcu);
62 return omnia_mcu_register_gpiochip(mcu);
66 +++ b/drivers/platform/cznic/turris-omnia-mcu-watchdog.c
68 +// SPDX-License-Identifier: GPL-2.0
70 + * CZ.NIC's Turris Omnia MCU watchdog driver
72 + * 2024 by Marek BehĂșn <kabel@kernel.org>
75 +#include <linux/bitops.h>
76 +#include <linux/device.h>
77 +#include <linux/i2c.h>
78 +#include <linux/moduleparam.h>
79 +#include <linux/types.h>
80 +#include <linux/units.h>
81 +#include <linux/watchdog.h>
83 +#include <linux/turris-omnia-mcu-interface.h>
84 +#include "turris-omnia-mcu.h"
86 +#define WATCHDOG_TIMEOUT 120
88 +static unsigned int timeout;
89 +module_param(timeout, int, 0);
90 +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds");
92 +static bool nowayout = WATCHDOG_NOWAYOUT;
93 +module_param(nowayout, bool, 0);
94 +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
95 + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
97 +static int omnia_wdt_start(struct watchdog_device *wdt)
99 + struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);
101 + return omnia_cmd_write_u8(mcu->client, OMNIA_CMD_SET_WATCHDOG_STATE, 1);
104 +static int omnia_wdt_stop(struct watchdog_device *wdt)
106 + struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);
108 + return omnia_cmd_write_u8(mcu->client, OMNIA_CMD_SET_WATCHDOG_STATE, 0);
111 +static int omnia_wdt_ping(struct watchdog_device *wdt)
113 + struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);
115 + return omnia_cmd_write_u8(mcu->client, OMNIA_CMD_SET_WATCHDOG_STATE, 1);
118 +static int omnia_wdt_set_timeout(struct watchdog_device *wdt,
119 + unsigned int timeout)
121 + struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);
123 + return omnia_cmd_write_u16(mcu->client, OMNIA_CMD_SET_WDT_TIMEOUT,
127 +static unsigned int omnia_wdt_get_timeleft(struct watchdog_device *wdt)
129 + struct omnia_mcu *mcu = watchdog_get_drvdata(wdt);
133 + err = omnia_cmd_read_u16(mcu->client, OMNIA_CMD_GET_WDT_TIMELEFT,
136 + dev_err(&mcu->client->dev, "Cannot get watchdog timeleft: %d\n",
141 + return timeleft / DECI;
144 +static const struct watchdog_info omnia_wdt_info = {
145 + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
146 + .identity = "Turris Omnia MCU Watchdog",
149 +static const struct watchdog_ops omnia_wdt_ops = {
150 + .owner = THIS_MODULE,
151 + .start = omnia_wdt_start,
152 + .stop = omnia_wdt_stop,
153 + .ping = omnia_wdt_ping,
154 + .set_timeout = omnia_wdt_set_timeout,
155 + .get_timeleft = omnia_wdt_get_timeleft,
158 +int omnia_mcu_register_watchdog(struct omnia_mcu *mcu)
160 + struct device *dev = &mcu->client->dev;
164 + if (!(mcu->features & OMNIA_FEAT_WDT_PING))
167 + mcu->wdt.info = &omnia_wdt_info;
168 + mcu->wdt.ops = &omnia_wdt_ops;
169 + mcu->wdt.parent = dev;
170 + mcu->wdt.min_timeout = 1;
171 + mcu->wdt.max_timeout = 65535 / DECI;
173 + mcu->wdt.timeout = WATCHDOG_TIMEOUT;
174 + watchdog_init_timeout(&mcu->wdt, timeout, dev);
176 + watchdog_set_drvdata(&mcu->wdt, mcu);
178 + omnia_wdt_set_timeout(&mcu->wdt, mcu->wdt.timeout);
180 + err = omnia_cmd_read_u8(mcu->client, OMNIA_CMD_GET_WATCHDOG_STATE,
183 + return dev_err_probe(dev, err,
184 + "Cannot get MCU watchdog state\n");
187 + set_bit(WDOG_HW_RUNNING, &mcu->wdt.status);
189 + watchdog_set_nowayout(&mcu->wdt, nowayout);
190 + watchdog_stop_on_reboot(&mcu->wdt);
191 + err = devm_watchdog_register_device(dev, &mcu->wdt);
193 + return dev_err_probe(dev, err,
194 + "Cannot register MCU watchdog\n");
198 --- a/drivers/platform/cznic/turris-omnia-mcu.h
199 +++ b/drivers/platform/cznic/turris-omnia-mcu.h
201 #include <linux/if_ether.h>
202 #include <linux/mutex.h>
203 #include <linux/types.h>
204 +#include <linux/watchdog.h>
205 #include <linux/workqueue.h>
206 #include <asm/byteorder.h>
207 #include <asm/unaligned.h>
208 @@ -43,6 +44,9 @@ struct omnia_mcu {
209 struct rtc_device *rtcdev;
211 bool front_button_poweron;
214 + struct watchdog_device wdt;
217 int omnia_cmd_write_read(const struct i2c_client *client,
218 @@ -55,6 +59,25 @@ static inline int omnia_cmd_write(const
219 return omnia_cmd_write_read(client, cmd, len, NULL, 0);
222 +static inline int omnia_cmd_write_u8(const struct i2c_client *client, u8 cmd,
225 + u8 buf[2] = { cmd, val };
227 + return omnia_cmd_write(client, buf, sizeof(buf));
230 +static inline int omnia_cmd_write_u16(const struct i2c_client *client, u8 cmd,
236 + put_unaligned_le16(val, &buf[1]);
238 + return omnia_cmd_write(client, buf, sizeof(buf));
241 static inline int omnia_cmd_write_u32(const struct i2c_client *client, u8 cmd,
244 @@ -158,5 +181,6 @@ extern const struct attribute_group omni
246 int omnia_mcu_register_gpiochip(struct omnia_mcu *mcu);
247 int omnia_mcu_register_sys_off_and_wakeup(struct omnia_mcu *mcu);
248 +int omnia_mcu_register_watchdog(struct omnia_mcu *mcu);
250 #endif /* __TURRIS_OMNIA_MCU_H */