Message ID | 20220502204050.88316-3-nick.hawkins@hpe.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | Introduce HPE GXP Architecture | expand |
On 5/2/22 13:40, nick.hawkins@hpe.com wrote: > From: Nick Hawkins <nick.hawkins@hpe.com> > > Adding support for the HPE GXP Watchdog. The GXP asic contains a full Add > compliment of timers one of which is the watchdog timer. The watchdog complement ? > timer is 16 bit and has 10ms resolution. The watchdog is created as a > child device of timer since the same register range is used. > > Signed-off-by: Nick Hawkins <nick.hawkins@hpe.com> > > --- > v6: > * No code change. > * Fixed commit subject line to match the ones in log. > * Adjusted commit message to be closer to 75 chars per line. > v5: > * Fixed version log > * Added details to Kconfig for module support. > * Adjusted commit messaged > v4: > * Made watchdog a child of timer as they share the same register region > per change request on dtsi. > * Removed extra parenthesis > * Fixed u8 u32 u64 usage > * Fixed alignment issue > * Reconfigured conditional statement for interrupt setup > * Removed unused gxp_wdt_remove function > v3: > * Put into proper patchset format > v2: > * No change > --- > drivers/watchdog/Kconfig | 11 +++ > drivers/watchdog/Makefile | 1 + > drivers/watchdog/gxp-wdt.c | 166 +++++++++++++++++++++++++++++++++++++ > 3 files changed, 178 insertions(+) > create mode 100644 drivers/watchdog/gxp-wdt.c > > diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig > index c4e82a8d863f..a591cc6aa152 100644 > --- a/drivers/watchdog/Kconfig > +++ b/drivers/watchdog/Kconfig > @@ -1820,6 +1820,17 @@ config RALINK_WDT > help > Hardware driver for the Ralink SoC Watchdog Timer. > > +config GXP_WATCHDOG > + tristate "HPE GXP watchdog support" > + depends on ARCH_HPE_GXP > + select WATCHDOG_CORE > + help > + Say Y here to include support for the watchdog timer > + in HPE GXP SoCs. > + > + To compile this driver as a module, choose M here. > + The module will be called gxp-wdt. > + > config MT7621_WDT > tristate "Mediatek SoC watchdog" > select WATCHDOG_CORE > diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile > index f7da867e8782..e2acf3a0d0fc 100644 > --- a/drivers/watchdog/Makefile > +++ b/drivers/watchdog/Makefile > @@ -92,6 +92,7 @@ obj-$(CONFIG_RTD119X_WATCHDOG) += rtd119x_wdt.o > obj-$(CONFIG_SPRD_WATCHDOG) += sprd_wdt.o > obj-$(CONFIG_PM8916_WATCHDOG) += pm8916_wdt.o > obj-$(CONFIG_ARM_SMC_WATCHDOG) += arm_smc_wdt.o > +obj-$(CONFIG_GXP_WATCHDOG) += gxp-wdt.o > obj-$(CONFIG_VISCONTI_WATCHDOG) += visconti_wdt.o > obj-$(CONFIG_MSC313E_WATCHDOG) += msc313e_wdt.o > obj-$(CONFIG_APPLE_WATCHDOG) += apple_wdt.o > diff --git a/drivers/watchdog/gxp-wdt.c b/drivers/watchdog/gxp-wdt.c > new file mode 100644 > index 000000000000..f45ab9a826d6 > --- /dev/null > +++ b/drivers/watchdog/gxp-wdt.c > @@ -0,0 +1,166 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* Copyright (C) 2022 Hewlett-Packard Enterprise Development Company, L.P. */ > + > +#include <linux/delay.h> > +#include <linux/io.h> > +#include <linux/module.h> > +#include <linux/platform_device.h> > +#include <linux/of_address.h> > +#include <linux/of_platform.h> Where are those of_ includes used ? > +#include <linux/types.h> > +#include <linux/watchdog.h> > + > +#define MASK_WDGCS_ENABLE 0x01 > +#define MASK_WDGCS_RELOAD 0x04 > +#define MASK_WDGCS_NMIEN 0x08 > +#define MASK_WDGCS_WARN 0x80 > + > +#define WDT_MAX_TIMEOUT_MS 655000 Shouldn't that be 655350 ? > +#define WDT_DEFAULT_TIMEOUT 30 > +#define SECS_TO_WDOG_TICKS(x) ((x) * 100) > +#define WDOG_TICKS_TO_SECS(x) ((x) / 100) > + > +#define GXP_WDT_CNT_OFS 0x10 > +#define GXP_WDT_CTRL_OFS 0x16 > + > +struct gxp_wdt { > + void __iomem *base; > + struct watchdog_device wdd; > +}; > + > +static void gxp_wdt_enable_reload(struct gxp_wdt *drvdata) > +{ > + u8 val; > + > + val = readb(drvdata->base + GXP_WDT_CTRL_OFS); > + val |= (MASK_WDGCS_ENABLE | MASK_WDGCS_RELOAD); > + writeb(val, drvdata->base + GXP_WDT_CTRL_OFS); > +} > + > +static int gxp_wdt_start(struct watchdog_device *wdd) > +{ > + struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd); > + > + writew(SECS_TO_WDOG_TICKS(wdd->timeout), drvdata->base + GXP_WDT_CNT_OFS); > + gxp_wdt_enable_reload(drvdata); > + return 0; > +} > + > +static int gxp_wdt_stop(struct watchdog_device *wdd) > +{ > + struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd); > + u8 val; > + > + val = readb_relaxed(drvdata->base + GXP_WDT_CTRL_OFS); > + val &= ~MASK_WDGCS_ENABLE; > + writeb(val, drvdata->base + GXP_WDT_CTRL_OFS); > + return 0; > +} > + > +static int gxp_wdt_set_timeout(struct watchdog_device *wdd, > + unsigned int timeout) > +{ > + struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd); > + u32 actual; > + > + wdd->timeout = timeout; > + actual = min(timeout, wdd->max_hw_heartbeat_ms / 1000); > + writew(SECS_TO_WDOG_TICKS(actual), drvdata->base + GXP_WDT_CNT_OFS); First, the accuracy of actual is reduced to 1 second, then SECS_TO_WDOG_TICKS() multiplies the result with 100, meaning the actual accuracy is 10ms. Why not just use 10 ms ? actual = min(timeout * 100, wdd->max_hw_heartbeat_ms / 10); writew(actual, drvdata->base + GXP_WDT_CNT_OFS); I guess it doesn't matter much since max_hw_heartbeat_ms is really a constant rounded down to seconds, it just looks odd. > + > + return 0; > +} > + > +static unsigned int gxp_wdt_get_timeleft(struct watchdog_device *wdd) > +{ > + struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd); > + u32 val = readw(drvdata->base + GXP_WDT_CNT_OFS); > + > + return WDOG_TICKS_TO_SECS(val); > +} > + > +static int gxp_wdt_ping(struct watchdog_device *wdd) > +{ > + struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd); > + > + gxp_wdt_enable_reload(drvdata); > + return 0; > +} > + > +static int gxp_restart(struct watchdog_device *wdd, unsigned long action, > + void *data) > +{ > + struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd); > + > + writew(10, drvdata->base + GXP_WDT_CNT_OFS); Doesn't that translate to 100 ms timeout ? Why such a large reboot delay instead of writing 1 ? > + gxp_wdt_enable_reload(drvdata); > + mdelay(100); > + return 0; > +} > + > +static const struct watchdog_ops gxp_wdt_ops = { > + .owner = THIS_MODULE, > + .start = gxp_wdt_start, > + .stop = gxp_wdt_stop, > + .ping = gxp_wdt_ping, > + .set_timeout = gxp_wdt_set_timeout, > + .get_timeleft = gxp_wdt_get_timeleft, > + .restart = gxp_restart, > +}; > + > +static const struct watchdog_info gxp_wdt_info = { > + .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, > + .identity = "HPE GXP Watchdog timer", > +}; > + > +static int gxp_wdt_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct gxp_wdt *drvdata; > + int err; > + u8 val; > + > + drvdata = devm_kzalloc(dev, sizeof(struct gxp_wdt), GFP_KERNEL); > + if (!drvdata) > + return -ENOMEM; > + > + drvdata->base = (void __iomem *)dev->platform_data; I'd personaly prefer if the address was passed as resource. > + > + drvdata->wdd.info = &gxp_wdt_info; > + drvdata->wdd.ops = &gxp_wdt_ops; > + drvdata->wdd.max_hw_heartbeat_ms = WDT_MAX_TIMEOUT_MS; > + drvdata->wdd.parent = dev; > + drvdata->wdd.timeout = WDT_DEFAULT_TIMEOUT; > + > + watchdog_set_drvdata(&drvdata->wdd, drvdata); > + watchdog_set_nowayout(&drvdata->wdd, WATCHDOG_NOWAYOUT); > + > + val = readb(drvdata->base + GXP_WDT_CTRL_OFS); > + > + if (val & MASK_WDGCS_ENABLE) > + set_bit(WDOG_HW_RUNNING, &drvdata->wdd.status); > + > + watchdog_set_restart_priority(&drvdata->wdd, 128); > + > + watchdog_stop_on_reboot(&drvdata->wdd); > + err = devm_watchdog_register_device(dev, &drvdata->wdd); > + if (err) { > + dev_err(dev, "Failed to register watchdog device"); > + return err; > + } > + > + dev_info(dev, "HPE GXP watchdog timer"); > + > + return 0; > +} > + > +static struct platform_driver gxp_wdt_driver = { > + .probe = gxp_wdt_probe, > + .driver = { > + .name = "gxp-wdt", > + }, > +}; > +module_platform_driver(gxp_wdt_driver); > + > +MODULE_AUTHOR("Nick Hawkins <nick.hawkins@hpe.com>"); > +MODULE_AUTHOR("Jean-Marie Verdun <verdun@hpe.com>"); > +MODULE_DESCRIPTION("Driver for GXP watchdog timer");
On 5/2/22 13:40, nick.hawkins@hpe.com wrote: > > +#include <linux/of_address.h> > > +#include <linux/of_platform.h> > Where are those of_ includes used ? They were not used anymore with latest changes. Thank you for pointing this out. I will remember to check in the future for each new commit to double check this. > > +#define WDT_MAX_TIMEOUT_MS 655000 > Shouldn't that be 655350 ? Yes it should be. I will correct this. > > +static int gxp_wdt_set_timeout(struct watchdog_device *wdd, > > + unsigned int timeout) > > +{ > > + struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd); > > + u32 actual; > > + > > + wdd->timeout = timeout; > > + actual = min(timeout, wdd->max_hw_heartbeat_ms / 1000); > > + writew(SECS_TO_WDOG_TICKS(actual), drvdata->base + GXP_WDT_CNT_OFS); > First, the accuracy of actual is reduced to 1 second, then SECS_TO_WDOG_TICKS() multiplies the result with 100, meaning the actual accuracy is 10ms. Why not just use 10 ms ? > actual = min(timeout * 100, wdd->max_hw_heartbeat_ms / 10); > writew(actual, drvdata->base + GXP_WDT_CNT_OFS); I have replaced the mention code with what you recommended above. > > + > > +static int gxp_restart(struct watchdog_device *wdd, unsigned long action, > > + void *data) > > +{ > > + struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd); > > + > > + writew(10, drvdata->base + GXP_WDT_CNT_OFS); > Doesn't that translate to 100 ms timeout ? Why such a large reboot delay instead of writing 1 ? This has been changed to 1. > > + gxp_wdt_enable_reload(drvdata); > > + mdelay(100); > > + return 0; > > +} > > + > > +static int gxp_wdt_probe(struct platform_device *pdev) { > > + struct device *dev = &pdev->dev; > > + struct gxp_wdt *drvdata; > > + int err; > > + u8 val; > > + > > + drvdata = devm_kzalloc(dev, sizeof(struct gxp_wdt), GFP_KERNEL); > > + if (!drvdata) > > + return -ENOMEM; > > + > > + drvdata->base = (void __iomem *)dev->platform_data; > I'd personaly prefer if the address was passed as resource. Just to clarify for my understanding are you asking that in the device structure I use the "void *platform_data" to pass "struct *resource"? If I am incorrect here can you elaborate on what you would like to be done? Based on feedback in review for the device tree; the watchdog is being created as a child to the timer. Therefore the conclusion reached was there should not be a gxp-wdt listed in the device tree files. I took this implementation based on what I found in ixp4xx_wdt.c. Thank you for your time and feedback Guenter, -Nick Hawkins
On 5/3/22 09:22, Hawkins, Nick wrote: > On 5/2/22 13:40, nick.hawkins@hpe.com wrote: >>> +#include <linux/of_address.h> >>> +#include <linux/of_platform.h> > >> Where are those of_ includes used ? > > They were not used anymore with latest changes. Thank you for pointing this out. I will remember to check in the future for each new commit to double check this. > >>> +#define WDT_MAX_TIMEOUT_MS 655000 > >> Shouldn't that be 655350 ? > > Yes it should be. I will correct this. > >>> +static int gxp_wdt_set_timeout(struct watchdog_device *wdd, >>> + unsigned int timeout) >>> +{ >>> + struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd); >>> + u32 actual; >>> + >>> + wdd->timeout = timeout; >>> + actual = min(timeout, wdd->max_hw_heartbeat_ms / 1000); >>> + writew(SECS_TO_WDOG_TICKS(actual), drvdata->base + GXP_WDT_CNT_OFS); > >> First, the accuracy of actual is reduced to 1 second, then SECS_TO_WDOG_TICKS() multiplies the result with 100, meaning the actual accuracy is 10ms. Why not just use 10 ms ? > >> actual = min(timeout * 100, wdd->max_hw_heartbeat_ms / 10); >> writew(actual, drvdata->base + GXP_WDT_CNT_OFS); > > I have replaced the mention code with what you recommended above. > >>> + >>> +static int gxp_restart(struct watchdog_device *wdd, unsigned long action, >>> + void *data) >>> +{ >>> + struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd); >>> + >>> + writew(10, drvdata->base + GXP_WDT_CNT_OFS); > >> Doesn't that translate to 100 ms timeout ? Why such a large reboot delay instead of writing 1 ? > > This has been changed to 1. > >>> + gxp_wdt_enable_reload(drvdata); >>> + mdelay(100); >>> + return 0; >>> +} >>> + >>> +static int gxp_wdt_probe(struct platform_device *pdev) { >>> + struct device *dev = &pdev->dev; >>> + struct gxp_wdt *drvdata; >>> + int err; >>> + u8 val; >>> + >>> + drvdata = devm_kzalloc(dev, sizeof(struct gxp_wdt), GFP_KERNEL); >>> + if (!drvdata) >>> + return -ENOMEM; >>> + >>> + drvdata->base = (void __iomem *)dev->platform_data; > >> I'd personaly prefer if the address was passed as resource. > > Just to clarify for my understanding are you asking that in the device structure I use the "void *platform_data" to pass "struct *resource"? If I am incorrect here can you elaborate on what you would like to be done? Based on feedback in review for the device tree; the watchdog is being created as a child to the timer. Therefore the conclusion reached was there should not be a gxp-wdt listed in the device tree files. I took this implementation based on what I found in ixp4xx_wdt.c. > One bad deed tends to multiply. No, I didn't ask to pass a struct resource as platform data. That would be no different to the current code. Resources can be added to a platform device using platform_device_add_resources(), and the platform driver can then use platform_get_resource() to use it. This would make it independent of a "private" mechanism. Thanks, Guenter
On Tue, May 3, 2022 at 6:53 PM Guenter Roeck <linux@roeck-us.net> wrote: > > Just to clarify for my understanding are you asking that in the device structure I use the "void *platform_data" to pass "struct *resource"? If I am incorrect here can you elaborate on what you would like to be done? Based on feedback in review for the device tree; the watchdog is being created as a child to the timer. Therefore the conclusion reached was there should not be a gxp-wdt listed in the device tree files. I took this implementation based on what I found in ixp4xx_wdt.c. > > > > One bad deed tends to multiply. > > No, I didn't ask to pass a struct resource as platform data. > That would be no different to the current code. Resources > can be added to a platform device using > platform_device_add_resources(), and the platform driver > can then use platform_get_resource() to use it. This > would make it independent of a "private" mechanism. Unfortunately there is no resource type for __iomem tokens, only for physical addresses, so you'd end up having to do ioremap() of the same address twice to map it into both the timer and the watchdog driver . Not the end of the world of course, but that doesn't seem much better than abusing the device private data. Arnd
On Tue, May 3, 2022 at 6:53 PM Guenter Roeck <linux@roeck-us.net> wrote: > > One bad deed tends to multiply. > > > > No, I didn't ask to pass a struct resource as platform data. > > That would be no different to the current code. Resources can be added > > to a platform device using platform_device_add_resources(), and the > > platform driver can then use platform_get_resource() to use it. This > > would make it independent of a "private" mechanism. > Unfortunately there is no resource type for __iomem tokens, only for physical addresses, so you'd end up having to do ioremap() of the same address twice to map it into both the timer and the watchdog driver . Not the end of the world of course, but that doesn't seem much better than abusing the device private data. Hello Guenter, Given Arnd's feedback would you like me to proceed with this change still or do you have another recommendation? Thanks, -Nick Hawkins
On Wed, May 04, 2022 at 04:25:59PM +0000, Hawkins, Nick wrote: > > On Tue, May 3, 2022 at 6:53 PM Guenter Roeck <linux@roeck-us.net> wrote: > > > > One bad deed tends to multiply. > > > > > > No, I didn't ask to pass a struct resource as platform data. > > > That would be no different to the current code. Resources can be added > > > to a platform device using platform_device_add_resources(), and the > > > platform driver can then use platform_get_resource() to use it. This > > > would make it independent of a "private" mechanism. > > > Unfortunately there is no resource type for __iomem tokens, only for physical addresses, so you'd end up having to do > ioremap() of the same address twice to map it into both the timer and the watchdog driver . Not the end of the world of course, but that doesn't seem much better than abusing the device private data. > > Hello Guenter, > > Given Arnd's feedback would you like me to proceed with this change still or do you have another recommendation? > Just leave it as is and add a note explaining why it is done that way. Guenter
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index c4e82a8d863f..a591cc6aa152 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -1820,6 +1820,17 @@ config RALINK_WDT help Hardware driver for the Ralink SoC Watchdog Timer. +config GXP_WATCHDOG + tristate "HPE GXP watchdog support" + depends on ARCH_HPE_GXP + select WATCHDOG_CORE + help + Say Y here to include support for the watchdog timer + in HPE GXP SoCs. + + To compile this driver as a module, choose M here. + The module will be called gxp-wdt. + config MT7621_WDT tristate "Mediatek SoC watchdog" select WATCHDOG_CORE diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index f7da867e8782..e2acf3a0d0fc 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -92,6 +92,7 @@ obj-$(CONFIG_RTD119X_WATCHDOG) += rtd119x_wdt.o obj-$(CONFIG_SPRD_WATCHDOG) += sprd_wdt.o obj-$(CONFIG_PM8916_WATCHDOG) += pm8916_wdt.o obj-$(CONFIG_ARM_SMC_WATCHDOG) += arm_smc_wdt.o +obj-$(CONFIG_GXP_WATCHDOG) += gxp-wdt.o obj-$(CONFIG_VISCONTI_WATCHDOG) += visconti_wdt.o obj-$(CONFIG_MSC313E_WATCHDOG) += msc313e_wdt.o obj-$(CONFIG_APPLE_WATCHDOG) += apple_wdt.o diff --git a/drivers/watchdog/gxp-wdt.c b/drivers/watchdog/gxp-wdt.c new file mode 100644 index 000000000000..f45ab9a826d6 --- /dev/null +++ b/drivers/watchdog/gxp-wdt.c @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2022 Hewlett-Packard Enterprise Development Company, L.P. */ + +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of_address.h> +#include <linux/of_platform.h> +#include <linux/types.h> +#include <linux/watchdog.h> + +#define MASK_WDGCS_ENABLE 0x01 +#define MASK_WDGCS_RELOAD 0x04 +#define MASK_WDGCS_NMIEN 0x08 +#define MASK_WDGCS_WARN 0x80 + +#define WDT_MAX_TIMEOUT_MS 655000 +#define WDT_DEFAULT_TIMEOUT 30 +#define SECS_TO_WDOG_TICKS(x) ((x) * 100) +#define WDOG_TICKS_TO_SECS(x) ((x) / 100) + +#define GXP_WDT_CNT_OFS 0x10 +#define GXP_WDT_CTRL_OFS 0x16 + +struct gxp_wdt { + void __iomem *base; + struct watchdog_device wdd; +}; + +static void gxp_wdt_enable_reload(struct gxp_wdt *drvdata) +{ + u8 val; + + val = readb(drvdata->base + GXP_WDT_CTRL_OFS); + val |= (MASK_WDGCS_ENABLE | MASK_WDGCS_RELOAD); + writeb(val, drvdata->base + GXP_WDT_CTRL_OFS); +} + +static int gxp_wdt_start(struct watchdog_device *wdd) +{ + struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd); + + writew(SECS_TO_WDOG_TICKS(wdd->timeout), drvdata->base + GXP_WDT_CNT_OFS); + gxp_wdt_enable_reload(drvdata); + return 0; +} + +static int gxp_wdt_stop(struct watchdog_device *wdd) +{ + struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd); + u8 val; + + val = readb_relaxed(drvdata->base + GXP_WDT_CTRL_OFS); + val &= ~MASK_WDGCS_ENABLE; + writeb(val, drvdata->base + GXP_WDT_CTRL_OFS); + return 0; +} + +static int gxp_wdt_set_timeout(struct watchdog_device *wdd, + unsigned int timeout) +{ + struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd); + u32 actual; + + wdd->timeout = timeout; + actual = min(timeout, wdd->max_hw_heartbeat_ms / 1000); + writew(SECS_TO_WDOG_TICKS(actual), drvdata->base + GXP_WDT_CNT_OFS); + + return 0; +} + +static unsigned int gxp_wdt_get_timeleft(struct watchdog_device *wdd) +{ + struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd); + u32 val = readw(drvdata->base + GXP_WDT_CNT_OFS); + + return WDOG_TICKS_TO_SECS(val); +} + +static int gxp_wdt_ping(struct watchdog_device *wdd) +{ + struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd); + + gxp_wdt_enable_reload(drvdata); + return 0; +} + +static int gxp_restart(struct watchdog_device *wdd, unsigned long action, + void *data) +{ + struct gxp_wdt *drvdata = watchdog_get_drvdata(wdd); + + writew(10, drvdata->base + GXP_WDT_CNT_OFS); + gxp_wdt_enable_reload(drvdata); + mdelay(100); + return 0; +} + +static const struct watchdog_ops gxp_wdt_ops = { + .owner = THIS_MODULE, + .start = gxp_wdt_start, + .stop = gxp_wdt_stop, + .ping = gxp_wdt_ping, + .set_timeout = gxp_wdt_set_timeout, + .get_timeleft = gxp_wdt_get_timeleft, + .restart = gxp_restart, +}; + +static const struct watchdog_info gxp_wdt_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, + .identity = "HPE GXP Watchdog timer", +}; + +static int gxp_wdt_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct gxp_wdt *drvdata; + int err; + u8 val; + + drvdata = devm_kzalloc(dev, sizeof(struct gxp_wdt), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->base = (void __iomem *)dev->platform_data; + + drvdata->wdd.info = &gxp_wdt_info; + drvdata->wdd.ops = &gxp_wdt_ops; + drvdata->wdd.max_hw_heartbeat_ms = WDT_MAX_TIMEOUT_MS; + drvdata->wdd.parent = dev; + drvdata->wdd.timeout = WDT_DEFAULT_TIMEOUT; + + watchdog_set_drvdata(&drvdata->wdd, drvdata); + watchdog_set_nowayout(&drvdata->wdd, WATCHDOG_NOWAYOUT); + + val = readb(drvdata->base + GXP_WDT_CTRL_OFS); + + if (val & MASK_WDGCS_ENABLE) + set_bit(WDOG_HW_RUNNING, &drvdata->wdd.status); + + watchdog_set_restart_priority(&drvdata->wdd, 128); + + watchdog_stop_on_reboot(&drvdata->wdd); + err = devm_watchdog_register_device(dev, &drvdata->wdd); + if (err) { + dev_err(dev, "Failed to register watchdog device"); + return err; + } + + dev_info(dev, "HPE GXP watchdog timer"); + + return 0; +} + +static struct platform_driver gxp_wdt_driver = { + .probe = gxp_wdt_probe, + .driver = { + .name = "gxp-wdt", + }, +}; +module_platform_driver(gxp_wdt_driver); + +MODULE_AUTHOR("Nick Hawkins <nick.hawkins@hpe.com>"); +MODULE_AUTHOR("Jean-Marie Verdun <verdun@hpe.com>"); +MODULE_DESCRIPTION("Driver for GXP watchdog timer");