| /* | 
 |  * watchdog driver for ZTE's zx2967 family | 
 |  * | 
 |  * Copyright (C) 2017 ZTE Ltd. | 
 |  * | 
 |  * Author: Baoyou Xie <baoyou.xie@linaro.org> | 
 |  * | 
 |  * License terms: GNU General Public License (GPL) version 2 | 
 |  */ | 
 |  | 
 | #include <linux/clk.h> | 
 | #include <linux/io.h> | 
 | #include <linux/mfd/syscon.h> | 
 | #include <linux/module.h> | 
 | #include <linux/of_address.h> | 
 | #include <linux/platform_device.h> | 
 | #include <linux/regmap.h> | 
 | #include <linux/reset.h> | 
 | #include <linux/watchdog.h> | 
 |  | 
 | #define ZX2967_WDT_CFG_REG			0x4 | 
 | #define ZX2967_WDT_LOAD_REG			0x8 | 
 | #define ZX2967_WDT_REFRESH_REG			0x18 | 
 | #define ZX2967_WDT_START_REG			0x1c | 
 |  | 
 | #define ZX2967_WDT_REFRESH_MASK			GENMASK(5, 0) | 
 |  | 
 | #define ZX2967_WDT_CFG_DIV(n)			((((n) & 0xff) - 1) << 8) | 
 | #define ZX2967_WDT_START_EN			0x1 | 
 |  | 
 | /* | 
 |  * Hardware magic number. | 
 |  * When watchdog reg is written, the lowest 16 bits are valid, but | 
 |  * the highest 16 bits should be always this number. | 
 |  */ | 
 | #define ZX2967_WDT_WRITEKEY			(0x1234 << 16) | 
 | #define ZX2967_WDT_VAL_MASK			GENMASK(15, 0) | 
 |  | 
 | #define ZX2967_WDT_DIV_DEFAULT			16 | 
 | #define ZX2967_WDT_DEFAULT_TIMEOUT		32 | 
 | #define ZX2967_WDT_MIN_TIMEOUT			1 | 
 | #define ZX2967_WDT_MAX_TIMEOUT			524 | 
 | #define ZX2967_WDT_MAX_COUNT			0xffff | 
 |  | 
 | #define ZX2967_WDT_CLK_FREQ			0x8000 | 
 |  | 
 | #define ZX2967_WDT_FLAG_REBOOT_MON		BIT(0) | 
 |  | 
 | struct zx2967_wdt { | 
 | 	struct watchdog_device	wdt_device; | 
 | 	void __iomem		*reg_base; | 
 | 	struct clk		*clock; | 
 | }; | 
 |  | 
 | static inline u32 zx2967_wdt_readl(struct zx2967_wdt *wdt, u16 reg) | 
 | { | 
 | 	return readl_relaxed(wdt->reg_base + reg); | 
 | } | 
 |  | 
 | static inline void zx2967_wdt_writel(struct zx2967_wdt *wdt, u16 reg, u32 val) | 
 | { | 
 | 	writel_relaxed(val | ZX2967_WDT_WRITEKEY, wdt->reg_base + reg); | 
 | } | 
 |  | 
 | static void zx2967_wdt_refresh(struct zx2967_wdt *wdt) | 
 | { | 
 | 	u32 val; | 
 |  | 
 | 	val = zx2967_wdt_readl(wdt, ZX2967_WDT_REFRESH_REG); | 
 | 	/* | 
 | 	 * Bit 4-5, 1 and 2: refresh config info | 
 | 	 * Bit 2-3, 1 and 2: refresh counter | 
 | 	 * Bit 0-1, 1 and 2: refresh int-value | 
 | 	 * we shift each group value between 1 and 2 to refresh all data. | 
 | 	 */ | 
 | 	val ^= ZX2967_WDT_REFRESH_MASK; | 
 | 	zx2967_wdt_writel(wdt, ZX2967_WDT_REFRESH_REG, | 
 | 			  val & ZX2967_WDT_VAL_MASK); | 
 | } | 
 |  | 
 | static int | 
 | zx2967_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout) | 
 | { | 
 | 	struct zx2967_wdt *wdt = watchdog_get_drvdata(wdd); | 
 | 	unsigned int divisor = ZX2967_WDT_DIV_DEFAULT; | 
 | 	u32 count; | 
 |  | 
 | 	count = timeout * ZX2967_WDT_CLK_FREQ; | 
 | 	if (count > divisor * ZX2967_WDT_MAX_COUNT) | 
 | 		divisor = DIV_ROUND_UP(count, ZX2967_WDT_MAX_COUNT); | 
 | 	count = DIV_ROUND_UP(count, divisor); | 
 | 	zx2967_wdt_writel(wdt, ZX2967_WDT_CFG_REG, | 
 | 			ZX2967_WDT_CFG_DIV(divisor) & ZX2967_WDT_VAL_MASK); | 
 | 	zx2967_wdt_writel(wdt, ZX2967_WDT_LOAD_REG, | 
 | 			count & ZX2967_WDT_VAL_MASK); | 
 | 	zx2967_wdt_refresh(wdt); | 
 | 	wdd->timeout =  (count * divisor) / ZX2967_WDT_CLK_FREQ; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void __zx2967_wdt_start(struct zx2967_wdt *wdt) | 
 | { | 
 | 	u32 val; | 
 |  | 
 | 	val = zx2967_wdt_readl(wdt, ZX2967_WDT_START_REG); | 
 | 	val |= ZX2967_WDT_START_EN; | 
 | 	zx2967_wdt_writel(wdt, ZX2967_WDT_START_REG, | 
 | 			val & ZX2967_WDT_VAL_MASK); | 
 | } | 
 |  | 
 | static void __zx2967_wdt_stop(struct zx2967_wdt *wdt) | 
 | { | 
 | 	u32 val; | 
 |  | 
 | 	val = zx2967_wdt_readl(wdt, ZX2967_WDT_START_REG); | 
 | 	val &= ~ZX2967_WDT_START_EN; | 
 | 	zx2967_wdt_writel(wdt, ZX2967_WDT_START_REG, | 
 | 			val & ZX2967_WDT_VAL_MASK); | 
 | } | 
 |  | 
 | static int zx2967_wdt_start(struct watchdog_device *wdd) | 
 | { | 
 | 	struct zx2967_wdt *wdt = watchdog_get_drvdata(wdd); | 
 |  | 
 | 	zx2967_wdt_set_timeout(wdd, wdd->timeout); | 
 | 	__zx2967_wdt_start(wdt); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int zx2967_wdt_stop(struct watchdog_device *wdd) | 
 | { | 
 | 	struct zx2967_wdt *wdt = watchdog_get_drvdata(wdd); | 
 |  | 
 | 	__zx2967_wdt_stop(wdt); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int zx2967_wdt_keepalive(struct watchdog_device *wdd) | 
 | { | 
 | 	struct zx2967_wdt *wdt = watchdog_get_drvdata(wdd); | 
 |  | 
 | 	zx2967_wdt_refresh(wdt); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | #define ZX2967_WDT_OPTIONS \ | 
 | 	(WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE) | 
 | static const struct watchdog_info zx2967_wdt_ident = { | 
 | 	.options          =     ZX2967_WDT_OPTIONS, | 
 | 	.identity         =	"zx2967 watchdog", | 
 | }; | 
 |  | 
 | static struct watchdog_ops zx2967_wdt_ops = { | 
 | 	.owner = THIS_MODULE, | 
 | 	.start = zx2967_wdt_start, | 
 | 	.stop = zx2967_wdt_stop, | 
 | 	.ping = zx2967_wdt_keepalive, | 
 | 	.set_timeout = zx2967_wdt_set_timeout, | 
 | }; | 
 |  | 
 | static void zx2967_wdt_reset_sysctrl(struct device *dev) | 
 | { | 
 | 	int ret; | 
 | 	void __iomem *regmap; | 
 | 	unsigned int offset, mask, config; | 
 | 	struct of_phandle_args out_args; | 
 |  | 
 | 	ret = of_parse_phandle_with_fixed_args(dev->of_node, | 
 | 			"zte,wdt-reset-sysctrl", 3, 0, &out_args); | 
 | 	if (ret) | 
 | 		return; | 
 |  | 
 | 	offset = out_args.args[0]; | 
 | 	config = out_args.args[1]; | 
 | 	mask = out_args.args[2]; | 
 |  | 
 | 	regmap = syscon_node_to_regmap(out_args.np); | 
 | 	if (IS_ERR(regmap)) { | 
 | 		of_node_put(out_args.np); | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	regmap_update_bits(regmap, offset, mask, config); | 
 | 	of_node_put(out_args.np); | 
 | } | 
 |  | 
 | static int zx2967_wdt_probe(struct platform_device *pdev) | 
 | { | 
 | 	struct device *dev = &pdev->dev; | 
 | 	struct zx2967_wdt *wdt; | 
 | 	struct resource *base; | 
 | 	int ret; | 
 | 	struct reset_control *rstc; | 
 |  | 
 | 	wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); | 
 | 	if (!wdt) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	platform_set_drvdata(pdev, wdt); | 
 |  | 
 | 	wdt->wdt_device.info = &zx2967_wdt_ident; | 
 | 	wdt->wdt_device.ops = &zx2967_wdt_ops; | 
 | 	wdt->wdt_device.timeout = ZX2967_WDT_DEFAULT_TIMEOUT; | 
 | 	wdt->wdt_device.max_timeout = ZX2967_WDT_MAX_TIMEOUT; | 
 | 	wdt->wdt_device.min_timeout = ZX2967_WDT_MIN_TIMEOUT; | 
 | 	wdt->wdt_device.parent = &pdev->dev; | 
 |  | 
 | 	base = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
 | 	wdt->reg_base = devm_ioremap_resource(dev, base); | 
 | 	if (IS_ERR(wdt->reg_base)) { | 
 | 		dev_err(dev, "ioremap failed\n"); | 
 | 		return PTR_ERR(wdt->reg_base); | 
 | 	} | 
 |  | 
 | 	zx2967_wdt_reset_sysctrl(dev); | 
 |  | 
 | 	wdt->clock = devm_clk_get(dev, NULL); | 
 | 	if (IS_ERR(wdt->clock)) { | 
 | 		dev_err(dev, "failed to find watchdog clock source\n"); | 
 | 		return PTR_ERR(wdt->clock); | 
 | 	} | 
 |  | 
 | 	ret = clk_prepare_enable(wdt->clock); | 
 | 	if (ret < 0) { | 
 | 		dev_err(dev, "failed to enable clock\n"); | 
 | 		return ret; | 
 | 	} | 
 | 	clk_set_rate(wdt->clock, ZX2967_WDT_CLK_FREQ); | 
 |  | 
 | 	rstc = devm_reset_control_get(dev, NULL); | 
 | 	if (IS_ERR(rstc)) { | 
 | 		dev_err(dev, "failed to get rstc"); | 
 | 		ret = PTR_ERR(rstc); | 
 | 		goto err; | 
 | 	} | 
 |  | 
 | 	reset_control_assert(rstc); | 
 | 	reset_control_deassert(rstc); | 
 |  | 
 | 	watchdog_set_drvdata(&wdt->wdt_device, wdt); | 
 | 	watchdog_init_timeout(&wdt->wdt_device, | 
 | 			ZX2967_WDT_DEFAULT_TIMEOUT, dev); | 
 | 	watchdog_set_nowayout(&wdt->wdt_device, WATCHDOG_NOWAYOUT); | 
 |  | 
 | 	ret = watchdog_register_device(&wdt->wdt_device); | 
 | 	if (ret) | 
 | 		goto err; | 
 |  | 
 | 	dev_info(dev, "watchdog enabled (timeout=%d sec, nowayout=%d)", | 
 | 		 wdt->wdt_device.timeout, WATCHDOG_NOWAYOUT); | 
 |  | 
 | 	return 0; | 
 |  | 
 | err: | 
 | 	clk_disable_unprepare(wdt->clock); | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int zx2967_wdt_remove(struct platform_device *pdev) | 
 | { | 
 | 	struct zx2967_wdt *wdt = platform_get_drvdata(pdev); | 
 |  | 
 | 	watchdog_unregister_device(&wdt->wdt_device); | 
 | 	clk_disable_unprepare(wdt->clock); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static const struct of_device_id zx2967_wdt_match[] = { | 
 | 	{ .compatible = "zte,zx296718-wdt", }, | 
 | 	{} | 
 | }; | 
 | MODULE_DEVICE_TABLE(of, zx2967_wdt_match); | 
 |  | 
 | static struct platform_driver zx2967_wdt_driver = { | 
 | 	.probe		= zx2967_wdt_probe, | 
 | 	.remove		= zx2967_wdt_remove, | 
 | 	.driver		= { | 
 | 		.name	= "zx2967-wdt", | 
 | 		.of_match_table	= of_match_ptr(zx2967_wdt_match), | 
 | 	}, | 
 | }; | 
 | module_platform_driver(zx2967_wdt_driver); | 
 |  | 
 | MODULE_AUTHOR("Baoyou Xie <baoyou.xie@linaro.org>"); | 
 | MODULE_DESCRIPTION("ZTE zx2967 Watchdog Device Driver"); | 
 | MODULE_LICENSE("GPL v2"); |