Commit ab89fb5f authored by Marek Behún's avatar Marek Behún Committed by Arnd Bergmann

platform: cznic: turris-omnia-mcu: Add support for MCU watchdog

Add support for the watchdog mechanism provided by the MCU.
Signed-off-by: default avatarMarek Behún <kabel@kernel.org>
Reviewed-by: default avatarAndy Shevchenko <andy@kernel.org>
Reviewed-by: default avatarGuenter Roeck <linux@roeck-us.net>
Acked-by: default avatarBartosz Golaszewski <bartosz.golaszewski@linaro.org>
Link: https://lore.kernel.org/r/20240701113010.16447-6-kabel@kernel.orgSigned-off-by: default avatarArnd Bergmann <arnd@arndb.de>
parent 90e700fd
...@@ -19,6 +19,7 @@ config TURRIS_OMNIA_MCU ...@@ -19,6 +19,7 @@ config TURRIS_OMNIA_MCU
select GPIOLIB select GPIOLIB
select GPIOLIB_IRQCHIP select GPIOLIB_IRQCHIP
select RTC_CLASS select RTC_CLASS
select WATCHDOG_CORE
help help
Say Y here to add support for the features implemented by the Say Y here to add support for the features implemented by the
microcontroller on the CZ.NIC's Turris Omnia SOHO router. microcontroller on the CZ.NIC's Turris Omnia SOHO router.
...@@ -26,6 +27,7 @@ config TURRIS_OMNIA_MCU ...@@ -26,6 +27,7 @@ config TURRIS_OMNIA_MCU
- board poweroff into true low power mode (with voltage regulators - board poweroff into true low power mode (with voltage regulators
disabled) and the ability to configure wake up from this mode (via disabled) and the ability to configure wake up from this mode (via
rtcwake) rtcwake)
- MCU watchdog
- GPIO pins - GPIO pins
- to get front button press events (the front button can be - to get front button press events (the front button can be
configured either to generate press events to the CPU or to change configured either to generate press events to the CPU or to change
......
...@@ -4,3 +4,4 @@ obj-$(CONFIG_TURRIS_OMNIA_MCU) += turris-omnia-mcu.o ...@@ -4,3 +4,4 @@ obj-$(CONFIG_TURRIS_OMNIA_MCU) += turris-omnia-mcu.o
turris-omnia-mcu-y := turris-omnia-mcu-base.o turris-omnia-mcu-y := turris-omnia-mcu-base.o
turris-omnia-mcu-y += turris-omnia-mcu-gpio.o turris-omnia-mcu-y += turris-omnia-mcu-gpio.o
turris-omnia-mcu-y += turris-omnia-mcu-sys-off-wakeup.o turris-omnia-mcu-y += turris-omnia-mcu-sys-off-wakeup.o
turris-omnia-mcu-y += turris-omnia-mcu-watchdog.o
...@@ -377,6 +377,10 @@ static int omnia_mcu_probe(struct i2c_client *client) ...@@ -377,6 +377,10 @@ static int omnia_mcu_probe(struct i2c_client *client)
if (err) if (err)
return err; return err;
err = omnia_mcu_register_watchdog(mcu);
if (err)
return err;
return omnia_mcu_register_gpiochip(mcu); return omnia_mcu_register_gpiochip(mcu);
} }
......
// 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;
}
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include <linux/if_ether.h> #include <linux/if_ether.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/types.h> #include <linux/types.h>
#include <linux/watchdog.h>
#include <linux/workqueue.h> #include <linux/workqueue.h>
#include <asm/byteorder.h> #include <asm/byteorder.h>
#include <asm/unaligned.h> #include <asm/unaligned.h>
...@@ -43,6 +44,9 @@ struct omnia_mcu { ...@@ -43,6 +44,9 @@ struct omnia_mcu {
struct rtc_device *rtcdev; struct rtc_device *rtcdev;
u32 rtc_alarm; u32 rtc_alarm;
bool front_button_poweron; bool front_button_poweron;
/* MCU watchdog */
struct watchdog_device wdt;
}; };
int omnia_cmd_write_read(const struct i2c_client *client, int omnia_cmd_write_read(const struct i2c_client *client,
...@@ -55,6 +59,25 @@ static inline int omnia_cmd_write(const struct i2c_client *client, void *cmd, ...@@ -55,6 +59,25 @@ static inline int omnia_cmd_write(const struct i2c_client *client, void *cmd,
return omnia_cmd_write_read(client, cmd, len, NULL, 0); return omnia_cmd_write_read(client, cmd, len, NULL, 0);
} }
static inline int omnia_cmd_write_u8(const struct i2c_client *client, u8 cmd,
u8 val)
{
u8 buf[2] = { cmd, val };
return omnia_cmd_write(client, buf, sizeof(buf));
}
static inline int omnia_cmd_write_u16(const struct i2c_client *client, u8 cmd,
u16 val)
{
u8 buf[3];
buf[0] = cmd;
put_unaligned_le16(val, &buf[1]);
return omnia_cmd_write(client, buf, sizeof(buf));
}
static inline int omnia_cmd_write_u32(const struct i2c_client *client, u8 cmd, static inline int omnia_cmd_write_u32(const struct i2c_client *client, u8 cmd,
u32 val) u32 val)
{ {
...@@ -158,5 +181,6 @@ extern const struct attribute_group omnia_mcu_poweroff_group; ...@@ -158,5 +181,6 @@ extern const struct attribute_group omnia_mcu_poweroff_group;
int omnia_mcu_register_gpiochip(struct omnia_mcu *mcu); int omnia_mcu_register_gpiochip(struct omnia_mcu *mcu);
int omnia_mcu_register_sys_off_and_wakeup(struct omnia_mcu *mcu); int omnia_mcu_register_sys_off_and_wakeup(struct omnia_mcu *mcu);
int omnia_mcu_register_watchdog(struct omnia_mcu *mcu);
#endif /* __TURRIS_OMNIA_MCU_H */ #endif /* __TURRIS_OMNIA_MCU_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment