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

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.org


Signed-off-by: default avatarArnd Bergmann <arnd@arndb.de>
parent 90e700fd
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ config TURRIS_OMNIA_MCU
	select GPIOLIB
	select GPIOLIB_IRQCHIP
	select RTC_CLASS
	select WATCHDOG_CORE
	help
	  Say Y here to add support for the features implemented by the
	  microcontroller on the CZ.NIC's Turris Omnia SOHO router.
@@ -26,6 +27,7 @@ config TURRIS_OMNIA_MCU
	  - board poweroff into true low power mode (with voltage regulators
	    disabled) and the ability to configure wake up from this mode (via
	    rtcwake)
	  - MCU watchdog
	  - GPIO pins
	    - to get front button press events (the front button can be
	      configured either to generate press events to the CPU or to change
+1 −0
Original line number Diff line number Diff line
@@ -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-gpio.o
turris-omnia-mcu-y		+= turris-omnia-mcu-sys-off-wakeup.o
turris-omnia-mcu-y		+= turris-omnia-mcu-watchdog.o
+4 −0
Original line number Diff line number Diff line
@@ -377,6 +377,10 @@ static int omnia_mcu_probe(struct i2c_client *client)
	if (err)
		return err;

	err = omnia_mcu_register_watchdog(mcu);
	if (err)
		return err;

	return omnia_mcu_register_gpiochip(mcu);
}

+130 −0
Original line number Diff line number Diff line
// 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;
}
+24 −0
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@
#include <linux/if_ether.h>
#include <linux/mutex.h>
#include <linux/types.h>
#include <linux/watchdog.h>
#include <linux/workqueue.h>
#include <asm/byteorder.h>
#include <asm/unaligned.h>
@@ -43,6 +44,9 @@ struct omnia_mcu {
	struct rtc_device *rtcdev;
	u32 rtc_alarm;
	bool front_button_poweron;

	/* MCU watchdog */
	struct watchdog_device wdt;
};

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,
	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,
				      u32 val)
{
@@ -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_sys_off_and_wakeup(struct omnia_mcu *mcu);
int omnia_mcu_register_watchdog(struct omnia_mcu *mcu);

#endif /* __TURRIS_OMNIA_MCU_H */