Message ID | 20240418121116.22184-6-kabel@kernel.org (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | Turris Omnia MCU driver | expand |
On Thu, Apr 18, 2024 at 02:11:10PM +0200, Marek Behún wrote: > Add support for the watchdog mechanism provided by the MCU. > > Signed-off-by: Marek Behún <kabel@kernel.org> This driver should reside in driver/watchdog and use a generic API such as regmap to access chip registers. Guenter > --- > drivers/platform/cznic/Kconfig | 2 + > drivers/platform/cznic/Makefile | 1 + > .../platform/cznic/turris-omnia-mcu-base.c | 4 + > .../cznic/turris-omnia-mcu-watchdog.c | 123 ++++++++++++++++++ > drivers/platform/cznic/turris-omnia-mcu.h | 24 ++++ > 5 files changed, 154 insertions(+) > create mode 100644 drivers/platform/cznic/turris-omnia-mcu-watchdog.c > > diff --git a/drivers/platform/cznic/Kconfig b/drivers/platform/cznic/Kconfig > index 0a752aa654fa..e2649cdecc38 100644 > --- a/drivers/platform/cznic/Kconfig > +++ b/drivers/platform/cznic/Kconfig > @@ -20,6 +20,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. > @@ -27,6 +28,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 > diff --git a/drivers/platform/cznic/Makefile b/drivers/platform/cznic/Makefile > index a185ef882e44..687f7718c0a1 100644 > --- a/drivers/platform/cznic/Makefile > +++ b/drivers/platform/cznic/Makefile > @@ -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 > diff --git a/drivers/platform/cznic/turris-omnia-mcu-base.c b/drivers/platform/cznic/turris-omnia-mcu-base.c > index 697ace090bda..5f88119d825c 100644 > --- a/drivers/platform/cznic/turris-omnia-mcu-base.c > +++ b/drivers/platform/cznic/turris-omnia-mcu-base.c > @@ -365,6 +365,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); > } > > diff --git a/drivers/platform/cznic/turris-omnia-mcu-watchdog.c b/drivers/platform/cznic/turris-omnia-mcu-watchdog.c > new file mode 100644 > index 000000000000..b0de9585da84 > --- /dev/null > +++ b/drivers/platform/cznic/turris-omnia-mcu-watchdog.c > @@ -0,0 +1,123 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * CZ.NIC's Turris Omnia MCU watchdog driver > + * > + * 2024 by Marek Behún <kabel@kernel.org> > + */ > + > +#include <linux/i2c.h> > +#include <linux/moduleparam.h> > +#include <linux/turris-omnia-mcu-interface.h> > +#include <linux/types.h> > +#include <linux/watchdog.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, 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, 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, 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, CMD_SET_WDT_TIMEOUT, > + timeout * 10); > +} > + > +static unsigned int omnia_wdt_get_timeleft(struct watchdog_device *wdt) > +{ > + struct omnia_mcu *mcu = watchdog_get_drvdata(wdt); > + int ret; > + > + ret = omnia_cmd_read_u16(mcu->client, CMD_GET_WDT_TIMELEFT); > + if (ret < 0) { > + dev_err(&mcu->client->dev, "Cannot get watchdog timeleft: %d\n", > + ret); > + return 0; > + } > + > + return ret / 10; > +} > + > +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; > + int ret; > + > + if (!(mcu->features & 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 = 6553; /* 65535 deciseconds */ > + > + 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); > + > + ret = omnia_cmd_read_u8(mcu->client, CMD_GET_WATCHDOG_STATE); > + if (ret < 0) > + return dev_err_probe(dev, ret, > + "Cannot get MCU watchdog state\n"); > + > + if (ret) > + set_bit(WDOG_HW_RUNNING, &mcu->wdt.status); > + > + watchdog_set_nowayout(&mcu->wdt, nowayout); > + watchdog_stop_on_reboot(&mcu->wdt); > + ret = devm_watchdog_register_device(dev, &mcu->wdt); > + if (ret) > + return dev_err_probe(dev, ret, > + "Cannot register MCU watchdog\n"); > + > + return 0; > +} > diff --git a/drivers/platform/cznic/turris-omnia-mcu.h b/drivers/platform/cznic/turris-omnia-mcu.h > index 6179a4f2069e..1838cb3d636e 100644 > --- a/drivers/platform/cznic/turris-omnia-mcu.h > +++ b/drivers/platform/cznic/turris-omnia-mcu.h > @@ -14,6 +14,7 @@ > #include <linux/mutex.h> > #include <linux/rtc.h> > #include <linux/types.h> > +#include <linux/watchdog.h> > #include <linux/workqueue.h> > #include <asm/byteorder.h> > #include <asm/unaligned.h> > @@ -41,6 +42,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, > @@ -53,6 +57,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) > { > @@ -129,5 +152,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 */ > -- > 2.43.2 > >
On Thu, 18 Apr 2024 06:26:04 -0700 Guenter Roeck <linux@roeck-us.net> wrote: > On Thu, Apr 18, 2024 at 02:11:10PM +0200, Marek Behún wrote: > > Add support for the watchdog mechanism provided by the MCU. > > > > Signed-off-by: Marek Behún <kabel@kernel.org> > > This driver should reside in driver/watchdog and use a generic API > such as regmap to access chip registers. Hi Guenter, I have these points against that: - the regmap API is not applicable in a sane way to the Turris Omnia MCU interface. I was trying to do this 5 years ago. An explanation can be found at https://www.spinics.net/lists/linux-leds/msg11583.html (the LED driver uses same interface as the MCU driver, LED controller is implemented by the MCU). so were the watchdog driver a separate module, we would either need to rape the regmap API to do this, or come up with a new API specific for Turris Omnia, which seems like an unnecessary complication just for one device - this watchdog is specific for Turris Omnia, the driver can not be reused anywhere else - there are several other multifunction drivers in kernel registering watchdog that do not live in drivers/watchdog/ - leaving it in platform/cznic/turris-omnia-mcu* keeps all these turris-omnia-mcu features together and they are all implemented by one .ko module Marek
diff --git a/drivers/platform/cznic/Kconfig b/drivers/platform/cznic/Kconfig index 0a752aa654fa..e2649cdecc38 100644 --- a/drivers/platform/cznic/Kconfig +++ b/drivers/platform/cznic/Kconfig @@ -20,6 +20,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. @@ -27,6 +28,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 diff --git a/drivers/platform/cznic/Makefile b/drivers/platform/cznic/Makefile index a185ef882e44..687f7718c0a1 100644 --- a/drivers/platform/cznic/Makefile +++ b/drivers/platform/cznic/Makefile @@ -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 diff --git a/drivers/platform/cznic/turris-omnia-mcu-base.c b/drivers/platform/cznic/turris-omnia-mcu-base.c index 697ace090bda..5f88119d825c 100644 --- a/drivers/platform/cznic/turris-omnia-mcu-base.c +++ b/drivers/platform/cznic/turris-omnia-mcu-base.c @@ -365,6 +365,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); } diff --git a/drivers/platform/cznic/turris-omnia-mcu-watchdog.c b/drivers/platform/cznic/turris-omnia-mcu-watchdog.c new file mode 100644 index 000000000000..b0de9585da84 --- /dev/null +++ b/drivers/platform/cznic/turris-omnia-mcu-watchdog.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * CZ.NIC's Turris Omnia MCU watchdog driver + * + * 2024 by Marek Behún <kabel@kernel.org> + */ + +#include <linux/i2c.h> +#include <linux/moduleparam.h> +#include <linux/turris-omnia-mcu-interface.h> +#include <linux/types.h> +#include <linux/watchdog.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, 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, 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, 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, CMD_SET_WDT_TIMEOUT, + timeout * 10); +} + +static unsigned int omnia_wdt_get_timeleft(struct watchdog_device *wdt) +{ + struct omnia_mcu *mcu = watchdog_get_drvdata(wdt); + int ret; + + ret = omnia_cmd_read_u16(mcu->client, CMD_GET_WDT_TIMELEFT); + if (ret < 0) { + dev_err(&mcu->client->dev, "Cannot get watchdog timeleft: %d\n", + ret); + return 0; + } + + return ret / 10; +} + +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; + int ret; + + if (!(mcu->features & 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 = 6553; /* 65535 deciseconds */ + + 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); + + ret = omnia_cmd_read_u8(mcu->client, CMD_GET_WATCHDOG_STATE); + if (ret < 0) + return dev_err_probe(dev, ret, + "Cannot get MCU watchdog state\n"); + + if (ret) + set_bit(WDOG_HW_RUNNING, &mcu->wdt.status); + + watchdog_set_nowayout(&mcu->wdt, nowayout); + watchdog_stop_on_reboot(&mcu->wdt); + ret = devm_watchdog_register_device(dev, &mcu->wdt); + if (ret) + return dev_err_probe(dev, ret, + "Cannot register MCU watchdog\n"); + + return 0; +} diff --git a/drivers/platform/cznic/turris-omnia-mcu.h b/drivers/platform/cznic/turris-omnia-mcu.h index 6179a4f2069e..1838cb3d636e 100644 --- a/drivers/platform/cznic/turris-omnia-mcu.h +++ b/drivers/platform/cznic/turris-omnia-mcu.h @@ -14,6 +14,7 @@ #include <linux/mutex.h> #include <linux/rtc.h> #include <linux/types.h> +#include <linux/watchdog.h> #include <linux/workqueue.h> #include <asm/byteorder.h> #include <asm/unaligned.h> @@ -41,6 +42,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, @@ -53,6 +57,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) { @@ -129,5 +152,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 */
Add support for the watchdog mechanism provided by the MCU. Signed-off-by: Marek Behún <kabel@kernel.org> --- drivers/platform/cznic/Kconfig | 2 + drivers/platform/cznic/Makefile | 1 + .../platform/cznic/turris-omnia-mcu-base.c | 4 + .../cznic/turris-omnia-mcu-watchdog.c | 123 ++++++++++++++++++ drivers/platform/cznic/turris-omnia-mcu.h | 24 ++++ 5 files changed, 154 insertions(+) create mode 100644 drivers/platform/cznic/turris-omnia-mcu-watchdog.c