diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index d9734ef..cf83b6a 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -22,6 +22,12 @@ help Add support for watchdog on the QCA AR9344 SoC. +config WATCHDOG_AT91SAM9 + bool "Watchdog for AT91SAM9 and SAMA5 SoCs" + depends on ARCH_AT91 + help + Support for the watchdog in AT91SAM9X and SAMA5D{2,3,4} SoCs. + config WATCHDOG_EFI bool "Generic EFI Watchdog Driver" depends on EFI_BOOTUP diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 3af64db..dc98427 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -1,5 +1,6 @@ obj-$(CONFIG_WATCHDOG) += wd_core.o obj-$(CONFIG_WATCHDOG_AR9344) += ar9344_wdt.o +obj-$(CONFIG_WATCHDOG_AT91SAM9) += at91sam9_wdt.o obj-$(CONFIG_WATCHDOG_EFI) += efi_wdt.o obj-$(CONFIG_WATCHDOG_DAVINCI) += davinci_wdt.o obj-$(CONFIG_WATCHDOG_OMAP) += omap_wdt.o diff --git a/drivers/watchdog/at91sam9_wdt.c b/drivers/watchdog/at91sam9_wdt.c new file mode 100644 index 0000000..3f554bf --- /dev/null +++ b/drivers/watchdog/at91sam9_wdt.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Pengutronix, Ahmad Fatoum + */ + +#include +#include +#include +#include +#include +#include + +#define MIN_WDT_TIMEOUT 1 +#define MAX_WDT_TIMEOUT 16 +#define SECS_TO_WDOG_TICKS(s) ((s) ? (((s) << 8) - 1) : 0) + +struct at91sam9x_wdt { + struct watchdog wdd; + void __iomem *base; +}; + +static inline void at91sam9x_wdt_ping(struct at91sam9x_wdt *wdt) +{ + writel(AT91_WDT_WDRSTT | AT91_WDT_KEY, wdt->base + AT91_WDT_CR); +} + +static int at91sam9x_wdt_set_timeout(struct watchdog *wdd, unsigned timeout) +{ + struct at91sam9x_wdt *wdt = container_of(wdd, struct at91sam9x_wdt, wdd); + u32 mr_old, mr_new; + + mr_old = readl(wdt->base + AT91_WDT_MR); + + if (!timeout) { + mr_new = mr_old | AT91_WDT_WDDIS; + writel(mr_new, wdt->base + AT91_WDT_MR); + return 0; + } + + mr_new = AT91_WDT_WDRSTEN + | AT91_WDT_WDDBGHLT | AT91_WDT_WDIDLEHLT + | AT91_WDT_WDD + | (SECS_TO_WDOG_TICKS(timeout) & AT91_WDT_WDV); + + if (mr_new != mr_old) + writel(mr_new, wdt->base + AT91_WDT_MR); + + at91sam9x_wdt_ping(wdt); + return 0; +} + +static inline bool at91sam9x_wdt_is_disabled(struct at91sam9x_wdt *wdt) +{ + return readl(wdt->base + AT91_WDT_MR) & AT91_WDT_WDDIS; +} + +static int at91sam9x_wdt_probe(struct device_d *dev) +{ + struct at91sam9x_wdt *wdt; + struct resource *iores; + struct clk *clk; + int ret; + + wdt = xzalloc(sizeof(*wdt)); + iores = dev_request_mem_resource(dev, 0); + if (IS_ERR(iores)) { + dev_err(dev, "could not get watchdog memory region\n"); + return PTR_ERR(iores); + } + wdt->base = IOMEM(iores->start); + clk = clk_get(dev, NULL); + if (WARN_ON(IS_ERR(clk))) + return PTR_ERR(clk); + + clk_enable(clk); + + wdt->wdd.set_timeout = at91sam9x_wdt_set_timeout; + wdt->wdd.timeout_max = MAX_WDT_TIMEOUT; + wdt->wdd.hwdev = dev; + + if (at91sam9x_wdt_is_disabled(wdt)) + wdt->wdd.running = WDOG_HW_NOT_RUNNING; + else + wdt->wdd.running = WDOG_HW_RUNNING; + + ret = watchdog_register(&wdt->wdd); + if (ret) + free(wdt); + + return ret; +} + +static const __maybe_unused struct of_device_id at91sam9x_wdt_dt_ids[] = { + { .compatible = "atmel,at91sam9260-wdt", }, + { .compatible = "atmel,sama5d4-wdt", }, + { /* sentinel */ }, +}; + +static struct driver_d at91sam9x_wdt_driver = { + .name = "at91sam9x-wdt", + .of_compatible = DRV_OF_COMPAT(at91sam9x_wdt_dt_ids), + .probe = at91sam9x_wdt_probe, +}; + +static int __init at91sam9x_wdt_init(void) +{ + return platform_driver_register(&at91sam9x_wdt_driver); +} +device_initcall(at91sam9x_wdt_init);