1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * Copyright (C) 2015 Mans Rullgard <mans@mansr.com> 4 * SMP86xx/SMP87xx Watchdog driver 5 */ 6 7#include <linux/bitops.h> 8#include <linux/clk.h> 9#include <linux/delay.h> 10#include <linux/io.h> 11#include <linux/kernel.h> 12#include <linux/module.h> 13#include <linux/moduleparam.h> 14#include <linux/mod_devicetable.h> 15#include <linux/platform_device.h> 16#include <linux/watchdog.h> 17 18#define DEFAULT_TIMEOUT 30 19 20static bool nowayout = WATCHDOG_NOWAYOUT; 21module_param(nowayout, bool, 0); 22MODULE_PARM_DESC(nowayout, 23 "Watchdog cannot be stopped once started (default=" 24 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 25 26static unsigned int timeout; 27module_param(timeout, int, 0); 28MODULE_PARM_DESC(timeout, "Watchdog timeout"); 29 30/* 31 * Counter counts down from programmed value. Reset asserts when 32 * the counter reaches 1. 33 */ 34#define WD_COUNTER 0 35 36#define WD_CONFIG 4 37#define WD_CONFIG_XTAL_IN BIT(0) 38#define WD_CONFIG_DISABLE BIT(31) 39 40struct tangox_wdt_device { 41 struct watchdog_device wdt; 42 void __iomem *base; 43 unsigned long clk_rate; 44 struct clk *clk; 45}; 46 47static int tangox_wdt_set_timeout(struct watchdog_device *wdt, 48 unsigned int new_timeout) 49{ 50 wdt->timeout = new_timeout; 51 52 return 0; 53} 54 55static int tangox_wdt_start(struct watchdog_device *wdt) 56{ 57 struct tangox_wdt_device *dev = watchdog_get_drvdata(wdt); 58 u32 ticks; 59 60 ticks = 1 + wdt->timeout * dev->clk_rate; 61 writel(ticks, dev->base + WD_COUNTER); 62 63 return 0; 64} 65 66static int tangox_wdt_stop(struct watchdog_device *wdt) 67{ 68 struct tangox_wdt_device *dev = watchdog_get_drvdata(wdt); 69 70 writel(0, dev->base + WD_COUNTER); 71 72 return 0; 73} 74 75static unsigned int tangox_wdt_get_timeleft(struct watchdog_device *wdt) 76{ 77 struct tangox_wdt_device *dev = watchdog_get_drvdata(wdt); 78 u32 count; 79 80 count = readl(dev->base + WD_COUNTER); 81 82 if (!count) 83 return 0; 84 85 return (count - 1) / dev->clk_rate; 86} 87 88static const struct watchdog_info tangox_wdt_info = { 89 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 90 .identity = "tangox watchdog", 91}; 92 93static int tangox_wdt_restart(struct watchdog_device *wdt, 94 unsigned long action, void *data) 95{ 96 struct tangox_wdt_device *dev = watchdog_get_drvdata(wdt); 97 98 writel(1, dev->base + WD_COUNTER); 99 100 return 0; 101} 102 103static const struct watchdog_ops tangox_wdt_ops = { 104 .start = tangox_wdt_start, 105 .stop = tangox_wdt_stop, 106 .set_timeout = tangox_wdt_set_timeout, 107 .get_timeleft = tangox_wdt_get_timeleft, 108 .restart = tangox_wdt_restart, 109}; 110 111static void tangox_clk_disable_unprepare(void *data) 112{ 113 clk_disable_unprepare(data); 114} 115 116static int tangox_wdt_probe(struct platform_device *pdev) 117{ 118 struct tangox_wdt_device *dev; 119 u32 config; 120 int err; 121 122 dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); 123 if (!dev) 124 return -ENOMEM; 125 126 dev->base = devm_platform_ioremap_resource(pdev, 0); 127 if (IS_ERR(dev->base)) 128 return PTR_ERR(dev->base); 129 130 dev->clk = devm_clk_get(&pdev->dev, NULL); 131 if (IS_ERR(dev->clk)) 132 return PTR_ERR(dev->clk); 133 134 err = clk_prepare_enable(dev->clk); 135 if (err) 136 return err; 137 err = devm_add_action_or_reset(&pdev->dev, 138 tangox_clk_disable_unprepare, dev->clk); 139 if (err) 140 return err; 141 142 dev->clk_rate = clk_get_rate(dev->clk); 143 if (!dev->clk_rate) 144 return -EINVAL; 145 146 dev->wdt.parent = &pdev->dev; 147 dev->wdt.info = &tangox_wdt_info; 148 dev->wdt.ops = &tangox_wdt_ops; 149 dev->wdt.timeout = DEFAULT_TIMEOUT; 150 dev->wdt.min_timeout = 1; 151 dev->wdt.max_hw_heartbeat_ms = (U32_MAX - 1) / dev->clk_rate; 152 153 watchdog_init_timeout(&dev->wdt, timeout, &pdev->dev); 154 watchdog_set_nowayout(&dev->wdt, nowayout); 155 watchdog_set_drvdata(&dev->wdt, dev); 156 157 /* 158 * Deactivate counter if disable bit is set to avoid 159 * accidental reset. 160 */ 161 config = readl(dev->base + WD_CONFIG); 162 if (config & WD_CONFIG_DISABLE) 163 writel(0, dev->base + WD_COUNTER); 164 165 writel(WD_CONFIG_XTAL_IN, dev->base + WD_CONFIG); 166 167 /* 168 * Mark as active and restart with configured timeout if 169 * already running. 170 */ 171 if (readl(dev->base + WD_COUNTER)) { 172 set_bit(WDOG_HW_RUNNING, &dev->wdt.status); 173 tangox_wdt_start(&dev->wdt); 174 } 175 176 watchdog_set_restart_priority(&dev->wdt, 128); 177 178 watchdog_stop_on_unregister(&dev->wdt); 179 err = devm_watchdog_register_device(&pdev->dev, &dev->wdt); 180 if (err) 181 return err; 182 183 platform_set_drvdata(pdev, dev); 184 185 dev_info(&pdev->dev, "SMP86xx/SMP87xx watchdog registered\n"); 186 187 return 0; 188} 189 190static const struct of_device_id tangox_wdt_dt_ids[] = { 191 { .compatible = "sigma,smp8642-wdt" }, 192 { .compatible = "sigma,smp8759-wdt" }, 193 { } 194}; 195MODULE_DEVICE_TABLE(of, tangox_wdt_dt_ids); 196 197static struct platform_driver tangox_wdt_driver = { 198 .probe = tangox_wdt_probe, 199 .driver = { 200 .name = "tangox-wdt", 201 .of_match_table = tangox_wdt_dt_ids, 202 }, 203}; 204 205module_platform_driver(tangox_wdt_driver); 206 207MODULE_AUTHOR("Mans Rullgard <mans@mansr.com>"); 208MODULE_DESCRIPTION("SMP86xx/SMP87xx Watchdog driver"); 209MODULE_LICENSE("GPL"); 210