/* * driver/watchdog/axp2xx_wdt.c * * Copyright (C) 2019 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #define pr_fmt(x) KBUILD_MODNAME ": " x #include #include #include #include #include #include /* for AXP2101_MODULE_EN (0x18) */ #define AXP2101_WATCHDOG_EN BIT(0) /* for AXP2101_WATCHDOG_CFG (0x19) */ #define AXP2101_WATCHDOG_CLR BIT(3) #define AXP2101_WATCHDOG_RST_CFG_MASK GENMASK(5, 4) #define AXP2101_WATCHDOG_RST_SHIFT 4 #define AXP2101_WATCHDOG_RST_IRQ (0 << AXP2101_WATCHDOG_RST_SHIFT) #define AXP2101_WATCHDOG_RST_IRQ_SYS (1 << AXP2101_WATCHDOG_RST_SHIFT) #define AXP2101_WATCHDOG_RST_IRQ_SYS_PWROK (2 << AXP2101_WATCHDOG_RST_SHIFT) #define AXP2101_WATCHDOG_RST_IRQ_SYS_PWRONOFF (3 << AXP2101_WATCHDOG_RST_SHIFT) #define AXP2101_WATCHDOG_TIMER_MASK GENMASK(2, 0) #define AXP2101_WATCHDOG_TIMER_1S 0 #define AXP2101_WATCHDOG_TIMER_2S 1 #define AXP2101_WATCHDOG_TIMER_4S 2 #define AXP2101_WATCHDOG_TIMER_8S 3 #define AXP2101_WATCHDOG_TIMER_16S 4 #define AXP2101_WATCHDOG_TIMER_32S 5 #define AXP2101_WATCHDOG_TIMER_64S 6 #define AXP2101_WATCHDOG_TIMER_128S 7 #define DEFAULT_HEART_BEAT_MS 4000 #define DEFAULT_TIMEOUT 5 struct axp2xx_watchdog { struct watchdog_device wd; struct regmap *regmap; struct axp20x_dev *axp20x; }; static int axp2101_wdt_start(struct watchdog_device *wdt) { struct axp2xx_watchdog *axp2xx_wdt = watchdog_get_drvdata(wdt); struct regmap *regmap = axp2xx_wdt->regmap; /* ping */ regmap_update_bits(regmap, AXP2101_WATCHDOG_CFG, AXP2101_WATCHDOG_CLR, AXP2101_WATCHDOG_CLR); regmap_update_bits(regmap, AXP2101_MODULE_EN, AXP2101_WATCHDOG_EN, AXP2101_WATCHDOG_EN); return 0; } static int axp2101_wdt_stop(struct watchdog_device *wdt) { struct axp2xx_watchdog *axp2xx_wdt = watchdog_get_drvdata(wdt); struct regmap *regmap = axp2xx_wdt->regmap; regmap_update_bits(regmap, AXP2101_MODULE_EN, AXP2101_WATCHDOG_EN, 0); return 0; } static int axp2101_wdt_ping(struct watchdog_device *wdt) { struct axp2xx_watchdog *axp2xx_wdt = watchdog_get_drvdata(wdt); struct regmap *regmap = axp2xx_wdt->regmap; regmap_update_bits(regmap, AXP2101_WATCHDOG_CFG, AXP2101_WATCHDOG_CLR, AXP2101_WATCHDOG_CLR); return 0; } int axp2101_wdt_set_timeout(struct watchdog_device *wdt, unsigned int sec) { struct axp2xx_watchdog *axp2xx_wdt = watchdog_get_drvdata(wdt); axp2101_wdt_stop(&axp2xx_wdt->wd); axp2xx_wdt->wd.timeout = sec; axp2101_wdt_start(&axp2xx_wdt->wd); return 0; } static struct watchdog_ops axp2101_wdt_ops = { .owner = THIS_MODULE, .start = axp2101_wdt_start, .stop = axp2101_wdt_stop, .ping = axp2101_wdt_ping, .set_timeout = axp2101_wdt_set_timeout, }; static struct watchdog_info axp2xx_wdt_info = { .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, .firmware_version = 1, .identity = "axp2xx_wdt", }; static int axp2xx_wdt_probe(struct platform_device *pd) { int ret; struct axp2xx_watchdog *axp2xx_wd; struct axp20x_dev *axp20x = dev_get_drvdata(pd->dev.parent); axp2xx_wd = devm_kzalloc(&pd->dev, sizeof(struct axp2xx_watchdog), GFP_KERNEL); if (!axp2xx_wd) { pr_err("can not request memory\n"); return -ENOMEM; } switch (axp20x->variant) { case AXP2101_ID: axp2xx_wd->wd.ops = &axp2101_wdt_ops; regmap_update_bits(axp20x->regmap, AXP2101_WATCHDOG_CFG, AXP2101_WATCHDOG_RST_CFG_MASK, AXP2101_WATCHDOG_RST_IRQ_SYS_PWRONOFF); regmap_update_bits(axp20x->regmap, AXP2101_WATCHDOG_CFG, AXP2101_WATCHDOG_TIMER_MASK, AXP2101_WATCHDOG_TIMER_4S); break; default: return -ENOTSUPP; } axp2xx_wd->wd.parent = &pd->dev; axp2xx_wd->wd.info = &axp2xx_wdt_info; axp2xx_wd->wd.max_hw_heartbeat_ms = DEFAULT_HEART_BEAT_MS; axp2xx_wd->regmap = axp20x->regmap; axp2xx_wd->axp20x = axp20x; ret = watchdog_init_timeout(&axp2xx_wd->wd, DEFAULT_TIMEOUT, &pd->dev); if (ret < 0) { pr_err("axp2xx_wdt set error timeout\n"); return ret; } platform_set_drvdata(pd, axp2xx_wd); watchdog_set_drvdata(&axp2xx_wd->wd, axp2xx_wd); ret = watchdog_register_device(&axp2xx_wd->wd); if (ret) pr_err("can not register axp2xx watchdog\n"); return ret; } static int axp2xx_wdt_remove(struct platform_device *pd) { struct axp2xx_watchdog *axp2xx_wdt = platform_get_drvdata(pd); watchdog_unregister_device(&axp2xx_wdt->wd); return 0; } #ifdef CONFIG_PM_SLEEP static int axp2xx_suspend(struct platform_device *pd, pm_message_t state) { struct axp2xx_watchdog *axp2xx_wdt = platform_get_drvdata(pd); if (watchdog_active(&axp2xx_wdt->wd)) axp2101_wdt_stop(&axp2xx_wdt->wd); return 0; } static int axp2xx_resume(struct platform_device *pd) { struct axp2xx_watchdog *axp2xx_wdt = platform_get_drvdata(pd); if (watchdog_active(&axp2xx_wdt->wd)) axp2101_wdt_start(&axp2xx_wdt->wd); return 0; } #endif static struct platform_driver axp2xx_wdt_driver = { .probe = axp2xx_wdt_probe, .remove = axp2xx_wdt_remove, #ifdef CONFIG_PM_SLEEP .suspend = axp2xx_suspend, .resume = axp2xx_resume, #endif .driver = { .name = "axp2xx-watchdog", }, }; static int __init axp2xx_wdt_init(void) { int ret; ret = platform_driver_register(&axp2xx_wdt_driver); if (ret) pr_err("register axp2xx wdt driver failed %d\n", ret); return ret; } static void __exit apx2xx_wdt_exit(void) { platform_driver_unregister(&axp2xx_wdt_driver); } subsys_initcall(axp2xx_wdt_init); MODULE_DESCRIPTION("xpower axp2xx watchdog driver"); MODULE_AUTHOR("F.Y "); MODULE_LICENSE("GPL v2");