12e62c498SMarcus Folkesson // SPDX-License-Identifier: GPL-2.0+ 2f27925a6SLee Jones /* 3f27925a6SLee Jones * ST's LPC Watchdog 4f27925a6SLee Jones * 5f27925a6SLee Jones * Copyright (C) 2014 STMicroelectronics -- All Rights Reserved 6f27925a6SLee Jones * 7f27925a6SLee Jones * Author: David Paris <david.paris@st.com> for STMicroelectronics 8f27925a6SLee Jones * Lee Jones <lee.jones@linaro.org> for STMicroelectronics 9f27925a6SLee Jones */ 10f27925a6SLee Jones 11f27925a6SLee Jones #include <linux/clk.h> 12f27925a6SLee Jones #include <linux/init.h> 13f27925a6SLee Jones #include <linux/io.h> 14f27925a6SLee Jones #include <linux/kernel.h> 15f27925a6SLee Jones #include <linux/mfd/syscon.h> 16f27925a6SLee Jones #include <linux/module.h> 17f27925a6SLee Jones #include <linux/of.h> 18f27925a6SLee Jones #include <linux/of_platform.h> 19f27925a6SLee Jones #include <linux/platform_device.h> 20f27925a6SLee Jones #include <linux/regmap.h> 21f27925a6SLee Jones #include <linux/watchdog.h> 22f27925a6SLee Jones 23f27925a6SLee Jones #include <dt-bindings/mfd/st-lpc.h> 24f27925a6SLee Jones 25f27925a6SLee Jones /* Low Power Alarm */ 26f27925a6SLee Jones #define LPC_LPA_LSB_OFF 0x410 27f27925a6SLee Jones #define LPC_LPA_START_OFF 0x418 28f27925a6SLee Jones 29f27925a6SLee Jones /* LPC as WDT */ 30f27925a6SLee Jones #define LPC_WDT_OFF 0x510 31f27925a6SLee Jones 32f27925a6SLee Jones static struct watchdog_device st_wdog_dev; 33f27925a6SLee Jones 34f27925a6SLee Jones struct st_wdog_syscfg { 35f27925a6SLee Jones unsigned int reset_type_reg; 36f27925a6SLee Jones unsigned int reset_type_mask; 37f27925a6SLee Jones unsigned int enable_reg; 38f27925a6SLee Jones unsigned int enable_mask; 39f27925a6SLee Jones }; 40f27925a6SLee Jones 41f27925a6SLee Jones struct st_wdog { 42f27925a6SLee Jones void __iomem *base; 43f27925a6SLee Jones struct device *dev; 44f27925a6SLee Jones struct regmap *regmap; 45f27925a6SLee Jones struct st_wdog_syscfg *syscfg; 46f27925a6SLee Jones struct clk *clk; 47f27925a6SLee Jones unsigned long clkrate; 48f27925a6SLee Jones bool warm_reset; 49f27925a6SLee Jones }; 50f27925a6SLee Jones 51f27925a6SLee Jones static struct st_wdog_syscfg stih407_syscfg = { 52f27925a6SLee Jones .enable_reg = 0x204, 53f27925a6SLee Jones .enable_mask = BIT(19), 54f27925a6SLee Jones }; 55f27925a6SLee Jones 56f27925a6SLee Jones static const struct of_device_id st_wdog_match[] = { 57f27925a6SLee Jones { 58f27925a6SLee Jones .compatible = "st,stih407-lpc", 59f27925a6SLee Jones .data = &stih407_syscfg, 60f27925a6SLee Jones }, 61f27925a6SLee Jones {}, 62f27925a6SLee Jones }; 63f27925a6SLee Jones MODULE_DEVICE_TABLE(of, st_wdog_match); 64f27925a6SLee Jones 65f27925a6SLee Jones static void st_wdog_setup(struct st_wdog *st_wdog, bool enable) 66f27925a6SLee Jones { 67f27925a6SLee Jones /* Type of watchdog reset - 0: Cold 1: Warm */ 68f27925a6SLee Jones if (st_wdog->syscfg->reset_type_reg) 69f27925a6SLee Jones regmap_update_bits(st_wdog->regmap, 70f27925a6SLee Jones st_wdog->syscfg->reset_type_reg, 71f27925a6SLee Jones st_wdog->syscfg->reset_type_mask, 72f27925a6SLee Jones st_wdog->warm_reset); 73f27925a6SLee Jones 74f27925a6SLee Jones /* Mask/unmask watchdog reset */ 75f27925a6SLee Jones regmap_update_bits(st_wdog->regmap, 76f27925a6SLee Jones st_wdog->syscfg->enable_reg, 77f27925a6SLee Jones st_wdog->syscfg->enable_mask, 78f27925a6SLee Jones enable ? 0 : st_wdog->syscfg->enable_mask); 79f27925a6SLee Jones } 80f27925a6SLee Jones 81f27925a6SLee Jones static void st_wdog_load_timer(struct st_wdog *st_wdog, unsigned int timeout) 82f27925a6SLee Jones { 83f27925a6SLee Jones unsigned long clkrate = st_wdog->clkrate; 84f27925a6SLee Jones 85f27925a6SLee Jones writel_relaxed(timeout * clkrate, st_wdog->base + LPC_LPA_LSB_OFF); 86f27925a6SLee Jones writel_relaxed(1, st_wdog->base + LPC_LPA_START_OFF); 87f27925a6SLee Jones } 88f27925a6SLee Jones 89f27925a6SLee Jones static int st_wdog_start(struct watchdog_device *wdd) 90f27925a6SLee Jones { 91f27925a6SLee Jones struct st_wdog *st_wdog = watchdog_get_drvdata(wdd); 92f27925a6SLee Jones 93f27925a6SLee Jones writel_relaxed(1, st_wdog->base + LPC_WDT_OFF); 94f27925a6SLee Jones 95f27925a6SLee Jones return 0; 96f27925a6SLee Jones } 97f27925a6SLee Jones 98f27925a6SLee Jones static int st_wdog_stop(struct watchdog_device *wdd) 99f27925a6SLee Jones { 100f27925a6SLee Jones struct st_wdog *st_wdog = watchdog_get_drvdata(wdd); 101f27925a6SLee Jones 102f27925a6SLee Jones writel_relaxed(0, st_wdog->base + LPC_WDT_OFF); 103f27925a6SLee Jones 104f27925a6SLee Jones return 0; 105f27925a6SLee Jones } 106f27925a6SLee Jones 107f27925a6SLee Jones static int st_wdog_set_timeout(struct watchdog_device *wdd, 108f27925a6SLee Jones unsigned int timeout) 109f27925a6SLee Jones { 110f27925a6SLee Jones struct st_wdog *st_wdog = watchdog_get_drvdata(wdd); 111f27925a6SLee Jones 112f27925a6SLee Jones wdd->timeout = timeout; 113f27925a6SLee Jones st_wdog_load_timer(st_wdog, timeout); 114f27925a6SLee Jones 115f27925a6SLee Jones return 0; 116f27925a6SLee Jones } 117f27925a6SLee Jones 118f27925a6SLee Jones static int st_wdog_keepalive(struct watchdog_device *wdd) 119f27925a6SLee Jones { 120f27925a6SLee Jones struct st_wdog *st_wdog = watchdog_get_drvdata(wdd); 121f27925a6SLee Jones 122f27925a6SLee Jones st_wdog_load_timer(st_wdog, wdd->timeout); 123f27925a6SLee Jones 124f27925a6SLee Jones return 0; 125f27925a6SLee Jones } 126f27925a6SLee Jones 127f27925a6SLee Jones static const struct watchdog_info st_wdog_info = { 128f27925a6SLee Jones .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 129f27925a6SLee Jones .identity = "ST LPC WDT", 130f27925a6SLee Jones }; 131f27925a6SLee Jones 132f27925a6SLee Jones static const struct watchdog_ops st_wdog_ops = { 133f27925a6SLee Jones .owner = THIS_MODULE, 134f27925a6SLee Jones .start = st_wdog_start, 135f27925a6SLee Jones .stop = st_wdog_stop, 136f27925a6SLee Jones .ping = st_wdog_keepalive, 137f27925a6SLee Jones .set_timeout = st_wdog_set_timeout, 138f27925a6SLee Jones }; 139f27925a6SLee Jones 140f27925a6SLee Jones static struct watchdog_device st_wdog_dev = { 141f27925a6SLee Jones .info = &st_wdog_info, 142f27925a6SLee Jones .ops = &st_wdog_ops, 143f27925a6SLee Jones }; 144f27925a6SLee Jones 145cfe9ee3aSGuenter Roeck static void st_clk_disable_unprepare(void *data) 146cfe9ee3aSGuenter Roeck { 147cfe9ee3aSGuenter Roeck clk_disable_unprepare(data); 148cfe9ee3aSGuenter Roeck } 149cfe9ee3aSGuenter Roeck 150f27925a6SLee Jones static int st_wdog_probe(struct platform_device *pdev) 151f27925a6SLee Jones { 152cfe9ee3aSGuenter Roeck struct device *dev = &pdev->dev; 153f27925a6SLee Jones const struct of_device_id *match; 154cfe9ee3aSGuenter Roeck struct device_node *np = dev->of_node; 155f27925a6SLee Jones struct st_wdog *st_wdog; 156f27925a6SLee Jones struct regmap *regmap; 157f27925a6SLee Jones struct clk *clk; 158f27925a6SLee Jones void __iomem *base; 159f27925a6SLee Jones uint32_t mode; 160f27925a6SLee Jones int ret; 161f27925a6SLee Jones 162f27925a6SLee Jones ret = of_property_read_u32(np, "st,lpc-mode", &mode); 163f27925a6SLee Jones if (ret) { 164cfe9ee3aSGuenter Roeck dev_err(dev, "An LPC mode must be provided\n"); 165f27925a6SLee Jones return -EINVAL; 166f27925a6SLee Jones } 167f27925a6SLee Jones 16879cb0976SLee Jones /* LPC can either run as a Clocksource or in RTC or WDT mode */ 169f27925a6SLee Jones if (mode != ST_LPC_MODE_WDT) 170f27925a6SLee Jones return -ENODEV; 171f27925a6SLee Jones 172cfe9ee3aSGuenter Roeck st_wdog = devm_kzalloc(dev, sizeof(*st_wdog), GFP_KERNEL); 173f27925a6SLee Jones if (!st_wdog) 174f27925a6SLee Jones return -ENOMEM; 175f27925a6SLee Jones 176cfe9ee3aSGuenter Roeck match = of_match_device(st_wdog_match, dev); 177f27925a6SLee Jones if (!match) { 178cfe9ee3aSGuenter Roeck dev_err(dev, "Couldn't match device\n"); 179f27925a6SLee Jones return -ENODEV; 180f27925a6SLee Jones } 181f27925a6SLee Jones st_wdog->syscfg = (struct st_wdog_syscfg *)match->data; 182f27925a6SLee Jones 1830f0a6a28SGuenter Roeck base = devm_platform_ioremap_resource(pdev, 0); 184f27925a6SLee Jones if (IS_ERR(base)) 185f27925a6SLee Jones return PTR_ERR(base); 186f27925a6SLee Jones 187f27925a6SLee Jones regmap = syscon_regmap_lookup_by_phandle(np, "st,syscfg"); 188f27925a6SLee Jones if (IS_ERR(regmap)) { 189cfe9ee3aSGuenter Roeck dev_err(dev, "No syscfg phandle specified\n"); 190f27925a6SLee Jones return PTR_ERR(regmap); 191f27925a6SLee Jones } 192f27925a6SLee Jones 193cfe9ee3aSGuenter Roeck clk = devm_clk_get(dev, NULL); 194f27925a6SLee Jones if (IS_ERR(clk)) { 195cfe9ee3aSGuenter Roeck dev_err(dev, "Unable to request clock\n"); 196f27925a6SLee Jones return PTR_ERR(clk); 197f27925a6SLee Jones } 198f27925a6SLee Jones 199cfe9ee3aSGuenter Roeck st_wdog->dev = dev; 200f27925a6SLee Jones st_wdog->base = base; 201f27925a6SLee Jones st_wdog->clk = clk; 202f27925a6SLee Jones st_wdog->regmap = regmap; 203f27925a6SLee Jones st_wdog->warm_reset = of_property_read_bool(np, "st,warm_reset"); 204f27925a6SLee Jones st_wdog->clkrate = clk_get_rate(st_wdog->clk); 205f27925a6SLee Jones 206f27925a6SLee Jones if (!st_wdog->clkrate) { 207cfe9ee3aSGuenter Roeck dev_err(dev, "Unable to fetch clock rate\n"); 208f27925a6SLee Jones return -EINVAL; 209f27925a6SLee Jones } 210f27925a6SLee Jones st_wdog_dev.max_timeout = 0xFFFFFFFF / st_wdog->clkrate; 211cfe9ee3aSGuenter Roeck st_wdog_dev.parent = dev; 212f27925a6SLee Jones 213f27925a6SLee Jones ret = clk_prepare_enable(clk); 214f27925a6SLee Jones if (ret) { 215cfe9ee3aSGuenter Roeck dev_err(dev, "Unable to enable clock\n"); 216f27925a6SLee Jones return ret; 217f27925a6SLee Jones } 218cfe9ee3aSGuenter Roeck ret = devm_add_action_or_reset(dev, st_clk_disable_unprepare, clk); 219cfe9ee3aSGuenter Roeck if (ret) 220cfe9ee3aSGuenter Roeck return ret; 221f27925a6SLee Jones 222f27925a6SLee Jones watchdog_set_drvdata(&st_wdog_dev, st_wdog); 223f27925a6SLee Jones watchdog_set_nowayout(&st_wdog_dev, WATCHDOG_NOWAYOUT); 224f27925a6SLee Jones 225f27925a6SLee Jones /* Init Watchdog timeout with value in DT */ 226cfe9ee3aSGuenter Roeck ret = watchdog_init_timeout(&st_wdog_dev, 0, dev); 227b4214185SWolfram Sang if (ret) 228f27925a6SLee Jones return ret; 229f27925a6SLee Jones 230cfe9ee3aSGuenter Roeck ret = devm_watchdog_register_device(dev, &st_wdog_dev); 231*7283b217SWolfram Sang if (ret) 232f27925a6SLee Jones return ret; 233f27925a6SLee Jones 234f27925a6SLee Jones st_wdog_setup(st_wdog, true); 235f27925a6SLee Jones 236cfe9ee3aSGuenter Roeck dev_info(dev, "LPC Watchdog driver registered, reset type is %s", 237f27925a6SLee Jones st_wdog->warm_reset ? "warm" : "cold"); 238f27925a6SLee Jones 239f27925a6SLee Jones return ret; 240f27925a6SLee Jones } 241f27925a6SLee Jones 242f27925a6SLee Jones static int st_wdog_remove(struct platform_device *pdev) 243f27925a6SLee Jones { 244f27925a6SLee Jones struct st_wdog *st_wdog = watchdog_get_drvdata(&st_wdog_dev); 245f27925a6SLee Jones 246f27925a6SLee Jones st_wdog_setup(st_wdog, false); 247f27925a6SLee Jones 248f27925a6SLee Jones return 0; 249f27925a6SLee Jones } 250f27925a6SLee Jones 251f27925a6SLee Jones #ifdef CONFIG_PM_SLEEP 252f27925a6SLee Jones static int st_wdog_suspend(struct device *dev) 253f27925a6SLee Jones { 254f27925a6SLee Jones struct st_wdog *st_wdog = watchdog_get_drvdata(&st_wdog_dev); 255f27925a6SLee Jones 256f27925a6SLee Jones if (watchdog_active(&st_wdog_dev)) 257f27925a6SLee Jones st_wdog_stop(&st_wdog_dev); 258f27925a6SLee Jones 259f27925a6SLee Jones st_wdog_setup(st_wdog, false); 260f27925a6SLee Jones 261f27925a6SLee Jones clk_disable(st_wdog->clk); 262f27925a6SLee Jones 263f27925a6SLee Jones return 0; 264f27925a6SLee Jones } 265f27925a6SLee Jones 266f27925a6SLee Jones static int st_wdog_resume(struct device *dev) 267f27925a6SLee Jones { 268f27925a6SLee Jones struct st_wdog *st_wdog = watchdog_get_drvdata(&st_wdog_dev); 269f27925a6SLee Jones int ret; 270f27925a6SLee Jones 271f27925a6SLee Jones ret = clk_enable(st_wdog->clk); 272f27925a6SLee Jones if (ret) { 273f27925a6SLee Jones dev_err(dev, "Unable to re-enable clock\n"); 274f27925a6SLee Jones watchdog_unregister_device(&st_wdog_dev); 275f27925a6SLee Jones clk_unprepare(st_wdog->clk); 276f27925a6SLee Jones return ret; 277f27925a6SLee Jones } 278f27925a6SLee Jones 279f27925a6SLee Jones st_wdog_setup(st_wdog, true); 280f27925a6SLee Jones 281f27925a6SLee Jones if (watchdog_active(&st_wdog_dev)) { 282f27925a6SLee Jones st_wdog_load_timer(st_wdog, st_wdog_dev.timeout); 283f27925a6SLee Jones st_wdog_start(&st_wdog_dev); 284f27925a6SLee Jones } 285f27925a6SLee Jones 286f27925a6SLee Jones return 0; 287f27925a6SLee Jones } 288f27925a6SLee Jones #endif 289f27925a6SLee Jones 290f27925a6SLee Jones static SIMPLE_DEV_PM_OPS(st_wdog_pm_ops, 291f27925a6SLee Jones st_wdog_suspend, 292f27925a6SLee Jones st_wdog_resume); 293f27925a6SLee Jones 294f27925a6SLee Jones static struct platform_driver st_wdog_driver = { 295f27925a6SLee Jones .driver = { 296f27925a6SLee Jones .name = "st-lpc-wdt", 297f27925a6SLee Jones .pm = &st_wdog_pm_ops, 298f27925a6SLee Jones .of_match_table = st_wdog_match, 299f27925a6SLee Jones }, 300f27925a6SLee Jones .probe = st_wdog_probe, 301f27925a6SLee Jones .remove = st_wdog_remove, 302f27925a6SLee Jones }; 303f27925a6SLee Jones module_platform_driver(st_wdog_driver); 304f27925a6SLee Jones 305f27925a6SLee Jones MODULE_AUTHOR("David Paris <david.paris@st.com>"); 306f27925a6SLee Jones MODULE_DESCRIPTION("ST LPC Watchdog Driver"); 307f27925a6SLee Jones MODULE_LICENSE("GPL"); 308