Message ID | 20231023143130.11602-6-kabel@kernel.org (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | Turris Omnia MCU driver | expand |
On 10/23/23 07:31, Marek Behún wrote: > Add support for the watchdog mechanism provided by the MCU. > > Signed-off-by: Marek Behún <kabel@kernel.org> Any reason for keeping this out of drivers/watchdog ? > --- > drivers/platform/cznic/Kconfig | 2 + > drivers/platform/cznic/Makefile | 1 + > .../platform/cznic/turris-omnia-mcu-base.c | 4 + > .../cznic/turris-omnia-mcu-watchdog.c | 122 ++++++++++++++++++ > drivers/platform/cznic/turris-omnia-mcu.h | 24 ++++ > 5 files changed, 153 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 6f1470d1f673..a43997a12d74 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-objs := turris-omnia-mcu-base.o > turris-omnia-mcu-objs += turris-omnia-mcu-gpio.o > turris-omnia-mcu-objs += turris-omnia-mcu-sys-off-wakeup.o > +turris-omnia-mcu-objs += 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 942061a0ee66..521397bfc022 100644 > --- a/drivers/platform/cznic/turris-omnia-mcu-base.c > +++ b/drivers/platform/cznic/turris-omnia-mcu-base.c > @@ -243,6 +243,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..6685166bc4c7 > --- /dev/null > +++ b/drivers/platform/cznic/turris-omnia-mcu-watchdog.c > @@ -0,0 +1,122 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * CZ.NIC's Turris Omnia MCU watchdog driver > + * > + * 2023 by Marek Behún <kabel@kernel.org> > + */ > + > +#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; > + Why check this here and not in the calling code ? > + 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 c6a8036e0534..db2d18d6eea8 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> > @@ -36,6 +37,9 @@ struct omnia_mcu { > struct rtc_device *rtcdev; > u32 rtc_alarm; > bool front_button_poweron; > + > + /* MCU watchdog */ > + struct watchdog_device wdt; This should be internal to the watchdog driver. > }; > > static inline int omnia_cmd_write(const struct i2c_client *client, void *cmd, > @@ -48,6 +52,25 @@ static inline int omnia_cmd_write(const struct i2c_client *client, void *cmd, > return ret < 0 ? ret : 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) > { > @@ -140,5 +163,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 */
On Mon, 23 Oct 2023 07:47:44 -0700 Guenter Roeck <linux@roeck-us.net> wrote: > On 10/23/23 07:31, Marek Behún wrote: > > Add support for the watchdog mechanism provided by the MCU. > > > > Signed-off-by: Marek Behún <kabel@kernel.org> > > Any reason for keeping this out of drivers/watchdog ? Because this whole thing is compiled as one driver, turris-omnia-mcu.ko. The whole driver is specific for one device, the Turris Omnia router. Splitting it into separate modules would be unnecessary, the driver is meant to provide all the Turris Omnia MCU functionality. I've seen this done in various platform drivers. Moreover, all of the functionality lives at one I2C address, so only one I2C client is used. Originally it was one .c file, but I split it into 4 files to make it easier to review. You can look at the other patches at https://patchwork.kernel.org/project/linux-soc/list/?series=795666 > > + if (!(mcu->features & FEAT_WDT_PING)) > > + return 0; > > + > > Why check this here and not in the calling code ? No particular reason, just wanted to keep the calling code cleaner. > > @@ -36,6 +37,9 @@ struct omnia_mcu { > > struct rtc_device *rtcdev; > > u32 rtc_alarm; > > bool front_button_poweron; > > + > > + /* MCU watchdog */ > > + struct watchdog_device wdt; > > This should be internal to the watchdog driver. As explained above, all functionality is provided in one kernel module. This makes sense here since the driver is meant to provide all MCU functionality and it is specific for Turris Omnia. Marek
On Mon, Oct 23, 2023 at 04:59:59PM +0200, Marek Behún wrote: > Guenter Roeck <linux@roeck-us.net> wrote: > > On 10/23/23 07:31, Marek Behún wrote: > > > Add support for the watchdog mechanism provided by the MCU. > > Any reason for keeping this out of drivers/watchdog ? > Because this whole thing is compiled as one driver, turris-omnia-mcu.ko. > The whole driver is specific for one device, the Turris Omnia router. > Splitting it into separate modules would be unnecessary, the driver is > meant to provide all the Turris Omnia MCU functionality. > I've seen this done in various platform drivers. > Moreover, all of the functionality lives at one I2C address, so only > one I2C client is used. That all sounds a bit like drivers/mfd...
On Mon, 23 Oct 2023 16:03:08 +0100 Mark Brown <broonie@kernel.org> wrote: > On Mon, Oct 23, 2023 at 04:59:59PM +0200, Marek Behún wrote: > > Guenter Roeck <linux@roeck-us.net> wrote: > > > On 10/23/23 07:31, Marek Behún wrote: > > > > > Add support for the watchdog mechanism provided by the MCU. > > > > Any reason for keeping this out of drivers/watchdog ? > > > Because this whole thing is compiled as one driver, turris-omnia-mcu.ko. > > The whole driver is specific for one device, the Turris Omnia router. > > Splitting it into separate modules would be unnecessary, the driver is > > meant to provide all the Turris Omnia MCU functionality. > > > I've seen this done in various platform drivers. > > > Moreover, all of the functionality lives at one I2C address, so only > > one I2C client is used. > > That all sounds a bit like drivers/mfd... Hi Guenter, I would have thought so as well, but several years ago I sent a driver into mfd, and I've been told (I don't remember by who anymore, but I can find it) that mfd is supposed to contain drivers for multifunction devices that can be contained on different boards, and that my driver should go to platform, since it is specific for one hardware. 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 6f1470d1f673..a43997a12d74 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-objs := turris-omnia-mcu-base.o turris-omnia-mcu-objs += turris-omnia-mcu-gpio.o turris-omnia-mcu-objs += turris-omnia-mcu-sys-off-wakeup.o +turris-omnia-mcu-objs += 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 942061a0ee66..521397bfc022 100644 --- a/drivers/platform/cznic/turris-omnia-mcu-base.c +++ b/drivers/platform/cznic/turris-omnia-mcu-base.c @@ -243,6 +243,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..6685166bc4c7 --- /dev/null +++ b/drivers/platform/cznic/turris-omnia-mcu-watchdog.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * CZ.NIC's Turris Omnia MCU watchdog driver + * + * 2023 by Marek Behún <kabel@kernel.org> + */ + +#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 c6a8036e0534..db2d18d6eea8 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> @@ -36,6 +37,9 @@ struct omnia_mcu { struct rtc_device *rtcdev; u32 rtc_alarm; bool front_button_poweron; + + /* MCU watchdog */ + struct watchdog_device wdt; }; static inline int omnia_cmd_write(const struct i2c_client *client, void *cmd, @@ -48,6 +52,25 @@ static inline int omnia_cmd_write(const struct i2c_client *client, void *cmd, return ret < 0 ? ret : 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) { @@ -140,5 +163,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 | 122 ++++++++++++++++++ drivers/platform/cznic/turris-omnia-mcu.h | 24 ++++ 5 files changed, 153 insertions(+) create mode 100644 drivers/platform/cznic/turris-omnia-mcu-watchdog.c