1/* 2 * Watchdog driver for CSR SiRFprimaII and SiRFatlasVI 3 * 4 * Copyright (c) 2013 Cambridge Silicon Radio Limited, a CSR plc group company. 5 * 6 * Licensed under GPLv2 or later. 7 */ 8 9#include <linux/module.h> 10#include <linux/watchdog.h> 11#include <linux/platform_device.h> 12#include <linux/moduleparam.h> 13#include <linux/of.h> 14#include <linux/io.h> 15#include <linux/uaccess.h> 16 17#define CLOCK_FREQ 1000000 18 19#define SIRFSOC_TIMER_COUNTER_LO 0x0000 20#define SIRFSOC_TIMER_MATCH_0 0x0008 21#define SIRFSOC_TIMER_INT_EN 0x0024 22#define SIRFSOC_TIMER_WATCHDOG_EN 0x0028 23#define SIRFSOC_TIMER_LATCH 0x0030 24#define SIRFSOC_TIMER_LATCHED_LO 0x0034 25 26#define SIRFSOC_TIMER_WDT_INDEX 5 27 28#define SIRFSOC_WDT_MIN_TIMEOUT 30 /* 30 secs */ 29#define SIRFSOC_WDT_MAX_TIMEOUT (10 * 60) /* 10 mins */ 30#define SIRFSOC_WDT_DEFAULT_TIMEOUT 30 /* 30 secs */ 31 32static unsigned int timeout = SIRFSOC_WDT_DEFAULT_TIMEOUT; 33static bool nowayout = WATCHDOG_NOWAYOUT; 34 35module_param(timeout, uint, 0); 36module_param(nowayout, bool, 0); 37 38MODULE_PARM_DESC(timeout, "Default watchdog timeout (in seconds)"); 39MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 40 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 41 42static unsigned int sirfsoc_wdt_gettimeleft(struct watchdog_device *wdd) 43{ 44 u32 counter, match; 45 void __iomem *wdt_base; 46 int time_left; 47 48 wdt_base = watchdog_get_drvdata(wdd); 49 counter = readl(wdt_base + SIRFSOC_TIMER_COUNTER_LO); 50 match = readl(wdt_base + 51 SIRFSOC_TIMER_MATCH_0 + (SIRFSOC_TIMER_WDT_INDEX << 2)); 52 53 time_left = match - counter; 54 55 return time_left / CLOCK_FREQ; 56} 57 58static int sirfsoc_wdt_updatetimeout(struct watchdog_device *wdd) 59{ 60 u32 counter, timeout_ticks; 61 void __iomem *wdt_base; 62 63 timeout_ticks = wdd->timeout * CLOCK_FREQ; 64 wdt_base = watchdog_get_drvdata(wdd); 65 66 /* Enable the latch before reading the LATCH_LO register */ 67 writel(1, wdt_base + SIRFSOC_TIMER_LATCH); 68 69 /* Set the TO value */ 70 counter = readl(wdt_base + SIRFSOC_TIMER_LATCHED_LO); 71 72 counter += timeout_ticks; 73 74 writel(counter, wdt_base + 75 SIRFSOC_TIMER_MATCH_0 + (SIRFSOC_TIMER_WDT_INDEX << 2)); 76 77 return 0; 78} 79 80static int sirfsoc_wdt_enable(struct watchdog_device *wdd) 81{ 82 void __iomem *wdt_base = watchdog_get_drvdata(wdd); 83 sirfsoc_wdt_updatetimeout(wdd); 84 85 /* 86 * NOTE: If interrupt is not enabled 87 * then WD-Reset doesn't get generated at all. 88 */ 89 writel(readl(wdt_base + SIRFSOC_TIMER_INT_EN) 90 | (1 << SIRFSOC_TIMER_WDT_INDEX), 91 wdt_base + SIRFSOC_TIMER_INT_EN); 92 writel(1, wdt_base + SIRFSOC_TIMER_WATCHDOG_EN); 93 94 return 0; 95} 96 97static int sirfsoc_wdt_disable(struct watchdog_device *wdd) 98{ 99 void __iomem *wdt_base = watchdog_get_drvdata(wdd); 100 101 writel(0, wdt_base + SIRFSOC_TIMER_WATCHDOG_EN); 102 writel(readl(wdt_base + SIRFSOC_TIMER_INT_EN) 103 & (~(1 << SIRFSOC_TIMER_WDT_INDEX)), 104 wdt_base + SIRFSOC_TIMER_INT_EN); 105 106 return 0; 107} 108 109static int sirfsoc_wdt_settimeout(struct watchdog_device *wdd, unsigned int to) 110{ 111 wdd->timeout = to; 112 sirfsoc_wdt_updatetimeout(wdd); 113 114 return 0; 115} 116 117#define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE) 118 119static const struct watchdog_info sirfsoc_wdt_ident = { 120 .options = OPTIONS, 121 .firmware_version = 0, 122 .identity = "SiRFSOC Watchdog", 123}; 124 125static struct watchdog_ops sirfsoc_wdt_ops = { 126 .owner = THIS_MODULE, 127 .start = sirfsoc_wdt_enable, 128 .stop = sirfsoc_wdt_disable, 129 .get_timeleft = sirfsoc_wdt_gettimeleft, 130 .ping = sirfsoc_wdt_updatetimeout, 131 .set_timeout = sirfsoc_wdt_settimeout, 132}; 133 134static struct watchdog_device sirfsoc_wdd = { 135 .info = &sirfsoc_wdt_ident, 136 .ops = &sirfsoc_wdt_ops, 137 .timeout = SIRFSOC_WDT_DEFAULT_TIMEOUT, 138 .min_timeout = SIRFSOC_WDT_MIN_TIMEOUT, 139 .max_timeout = SIRFSOC_WDT_MAX_TIMEOUT, 140}; 141 142static int sirfsoc_wdt_probe(struct platform_device *pdev) 143{ 144 struct resource *res; 145 int ret; 146 void __iomem *base; 147 148 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 149 base = devm_ioremap_resource(&pdev->dev, res); 150 if (IS_ERR(base)) 151 return PTR_ERR(base); 152 153 watchdog_set_drvdata(&sirfsoc_wdd, base); 154 155 watchdog_init_timeout(&sirfsoc_wdd, timeout, &pdev->dev); 156 watchdog_set_nowayout(&sirfsoc_wdd, nowayout); 157 158 ret = watchdog_register_device(&sirfsoc_wdd); 159 if (ret) 160 return ret; 161 162 platform_set_drvdata(pdev, &sirfsoc_wdd); 163 164 return 0; 165} 166 167static void sirfsoc_wdt_shutdown(struct platform_device *pdev) 168{ 169 struct watchdog_device *wdd = platform_get_drvdata(pdev); 170 171 sirfsoc_wdt_disable(wdd); 172} 173 174static int sirfsoc_wdt_remove(struct platform_device *pdev) 175{ 176 sirfsoc_wdt_shutdown(pdev); 177 return 0; 178} 179 180#ifdef CONFIG_PM_SLEEP 181static int sirfsoc_wdt_suspend(struct device *dev) 182{ 183 return 0; 184} 185 186static int sirfsoc_wdt_resume(struct device *dev) 187{ 188 struct watchdog_device *wdd = dev_get_drvdata(dev); 189 190 /* 191 * NOTE: Since timer controller registers settings are saved 192 * and restored back by the timer-prima2.c, so we need not 193 * update WD settings except refreshing timeout. 194 */ 195 sirfsoc_wdt_updatetimeout(wdd); 196 197 return 0; 198} 199#endif 200 201static SIMPLE_DEV_PM_OPS(sirfsoc_wdt_pm_ops, 202 sirfsoc_wdt_suspend, sirfsoc_wdt_resume); 203 204static const struct of_device_id sirfsoc_wdt_of_match[] = { 205 { .compatible = "sirf,prima2-tick"}, 206 {}, 207}; 208MODULE_DEVICE_TABLE(of, sirfsoc_wdt_of_match); 209 210static struct platform_driver sirfsoc_wdt_driver = { 211 .driver = { 212 .name = "sirfsoc-wdt", 213 .owner = THIS_MODULE, 214 .pm = &sirfsoc_wdt_pm_ops, 215 .of_match_table = sirfsoc_wdt_of_match, 216 }, 217 .probe = sirfsoc_wdt_probe, 218 .remove = sirfsoc_wdt_remove, 219 .shutdown = sirfsoc_wdt_shutdown, 220}; 221module_platform_driver(sirfsoc_wdt_driver); 222 223MODULE_DESCRIPTION("SiRF SoC watchdog driver"); 224MODULE_AUTHOR("Xianglong Du <Xianglong.Du@csr.com>"); 225MODULE_LICENSE("GPL v2"); 226MODULE_ALIAS("platform:sirfsoc-wdt"); 227