12e62c498SMarcus Folkesson // SPDX-License-Identifier: GPL-2.0+ 2336694a0SBaruch Siach /* 3336694a0SBaruch Siach * Watchdog driver for Conexant Digicolor 4336694a0SBaruch Siach * 5336694a0SBaruch Siach * Copyright (C) 2015 Paradox Innovation Ltd. 6336694a0SBaruch Siach * 7336694a0SBaruch Siach */ 8336694a0SBaruch Siach 9336694a0SBaruch Siach #include <linux/types.h> 10336694a0SBaruch Siach #include <linux/module.h> 11336694a0SBaruch Siach #include <linux/io.h> 12336694a0SBaruch Siach #include <linux/delay.h> 13336694a0SBaruch Siach #include <linux/clk.h> 14336694a0SBaruch Siach #include <linux/watchdog.h> 15336694a0SBaruch Siach #include <linux/platform_device.h> 16336694a0SBaruch Siach #include <linux/of_address.h> 17336694a0SBaruch Siach 18336694a0SBaruch Siach #define TIMER_A_CONTROL 0 19336694a0SBaruch Siach #define TIMER_A_COUNT 4 20336694a0SBaruch Siach 21336694a0SBaruch Siach #define TIMER_A_ENABLE_COUNT BIT(0) 22336694a0SBaruch Siach #define TIMER_A_ENABLE_WATCHDOG BIT(1) 23336694a0SBaruch Siach 24336694a0SBaruch Siach struct dc_wdt { 25336694a0SBaruch Siach void __iomem *base; 26336694a0SBaruch Siach struct clk *clk; 27336694a0SBaruch Siach spinlock_t lock; 28336694a0SBaruch Siach }; 29336694a0SBaruch Siach 30336694a0SBaruch Siach static unsigned timeout; 31336694a0SBaruch Siach module_param(timeout, uint, 0); 32336694a0SBaruch Siach MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds"); 33336694a0SBaruch Siach 34336694a0SBaruch Siach static void dc_wdt_set(struct dc_wdt *wdt, u32 ticks) 35336694a0SBaruch Siach { 36336694a0SBaruch Siach unsigned long flags; 37336694a0SBaruch Siach 38336694a0SBaruch Siach spin_lock_irqsave(&wdt->lock, flags); 39336694a0SBaruch Siach 40336694a0SBaruch Siach writel_relaxed(0, wdt->base + TIMER_A_CONTROL); 41336694a0SBaruch Siach writel_relaxed(ticks, wdt->base + TIMER_A_COUNT); 42336694a0SBaruch Siach writel_relaxed(TIMER_A_ENABLE_COUNT | TIMER_A_ENABLE_WATCHDOG, 43336694a0SBaruch Siach wdt->base + TIMER_A_CONTROL); 44336694a0SBaruch Siach 45336694a0SBaruch Siach spin_unlock_irqrestore(&wdt->lock, flags); 46336694a0SBaruch Siach } 47336694a0SBaruch Siach 484d8b229dSGuenter Roeck static int dc_wdt_restart(struct watchdog_device *wdog, unsigned long action, 494d8b229dSGuenter Roeck void *data) 50336694a0SBaruch Siach { 51d3b08185SDamien Riegel struct dc_wdt *wdt = watchdog_get_drvdata(wdog); 52336694a0SBaruch Siach 53336694a0SBaruch Siach dc_wdt_set(wdt, 1); 54336694a0SBaruch Siach /* wait for reset to assert... */ 55336694a0SBaruch Siach mdelay(500); 56336694a0SBaruch Siach 57d3b08185SDamien Riegel return 0; 58336694a0SBaruch Siach } 59336694a0SBaruch Siach 60336694a0SBaruch Siach static int dc_wdt_start(struct watchdog_device *wdog) 61336694a0SBaruch Siach { 62336694a0SBaruch Siach struct dc_wdt *wdt = watchdog_get_drvdata(wdog); 63336694a0SBaruch Siach 64336694a0SBaruch Siach dc_wdt_set(wdt, wdog->timeout * clk_get_rate(wdt->clk)); 65336694a0SBaruch Siach 66336694a0SBaruch Siach return 0; 67336694a0SBaruch Siach } 68336694a0SBaruch Siach 69336694a0SBaruch Siach static int dc_wdt_stop(struct watchdog_device *wdog) 70336694a0SBaruch Siach { 71336694a0SBaruch Siach struct dc_wdt *wdt = watchdog_get_drvdata(wdog); 72336694a0SBaruch Siach 73336694a0SBaruch Siach writel_relaxed(0, wdt->base + TIMER_A_CONTROL); 74336694a0SBaruch Siach 75336694a0SBaruch Siach return 0; 76336694a0SBaruch Siach } 77336694a0SBaruch Siach 78336694a0SBaruch Siach static int dc_wdt_set_timeout(struct watchdog_device *wdog, unsigned int t) 79336694a0SBaruch Siach { 80336694a0SBaruch Siach struct dc_wdt *wdt = watchdog_get_drvdata(wdog); 81336694a0SBaruch Siach 82336694a0SBaruch Siach dc_wdt_set(wdt, t * clk_get_rate(wdt->clk)); 83336694a0SBaruch Siach wdog->timeout = t; 84336694a0SBaruch Siach 85336694a0SBaruch Siach return 0; 86336694a0SBaruch Siach } 87336694a0SBaruch Siach 88336694a0SBaruch Siach static unsigned int dc_wdt_get_timeleft(struct watchdog_device *wdog) 89336694a0SBaruch Siach { 90336694a0SBaruch Siach struct dc_wdt *wdt = watchdog_get_drvdata(wdog); 91336694a0SBaruch Siach uint32_t count = readl_relaxed(wdt->base + TIMER_A_COUNT); 92336694a0SBaruch Siach 93336694a0SBaruch Siach return count / clk_get_rate(wdt->clk); 94336694a0SBaruch Siach } 95336694a0SBaruch Siach 96b893e344SBhumika Goyal static const struct watchdog_ops dc_wdt_ops = { 97336694a0SBaruch Siach .owner = THIS_MODULE, 98336694a0SBaruch Siach .start = dc_wdt_start, 99336694a0SBaruch Siach .stop = dc_wdt_stop, 100336694a0SBaruch Siach .set_timeout = dc_wdt_set_timeout, 101336694a0SBaruch Siach .get_timeleft = dc_wdt_get_timeleft, 102d3b08185SDamien Riegel .restart = dc_wdt_restart, 103336694a0SBaruch Siach }; 104336694a0SBaruch Siach 1056c368932SBhumika Goyal static const struct watchdog_info dc_wdt_info = { 106336694a0SBaruch Siach .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE 107336694a0SBaruch Siach | WDIOF_KEEPALIVEPING, 108336694a0SBaruch Siach .identity = "Conexant Digicolor Watchdog", 109336694a0SBaruch Siach }; 110336694a0SBaruch Siach 111336694a0SBaruch Siach static struct watchdog_device dc_wdt_wdd = { 112336694a0SBaruch Siach .info = &dc_wdt_info, 113336694a0SBaruch Siach .ops = &dc_wdt_ops, 114336694a0SBaruch Siach .min_timeout = 1, 115336694a0SBaruch Siach }; 116336694a0SBaruch Siach 117336694a0SBaruch Siach static int dc_wdt_probe(struct platform_device *pdev) 118336694a0SBaruch Siach { 119336694a0SBaruch Siach struct device *dev = &pdev->dev; 120336694a0SBaruch Siach struct dc_wdt *wdt; 121336694a0SBaruch Siach 122336694a0SBaruch Siach wdt = devm_kzalloc(dev, sizeof(struct dc_wdt), GFP_KERNEL); 123336694a0SBaruch Siach if (!wdt) 124336694a0SBaruch Siach return -ENOMEM; 125336694a0SBaruch Siach 1260f0a6a28SGuenter Roeck wdt->base = devm_platform_ioremap_resource(pdev, 0); 127dd36f6ceSGuenter Roeck if (IS_ERR(wdt->base)) 128dd36f6ceSGuenter Roeck return PTR_ERR(wdt->base); 129336694a0SBaruch Siach 130dd36f6ceSGuenter Roeck wdt->clk = devm_clk_get(dev, NULL); 131dd36f6ceSGuenter Roeck if (IS_ERR(wdt->clk)) 132dd36f6ceSGuenter Roeck return PTR_ERR(wdt->clk); 133336694a0SBaruch Siach dc_wdt_wdd.max_timeout = U32_MAX / clk_get_rate(wdt->clk); 134336694a0SBaruch Siach dc_wdt_wdd.timeout = dc_wdt_wdd.max_timeout; 135dd36f6ceSGuenter Roeck dc_wdt_wdd.parent = dev; 136336694a0SBaruch Siach 137336694a0SBaruch Siach spin_lock_init(&wdt->lock); 138336694a0SBaruch Siach 139336694a0SBaruch Siach watchdog_set_drvdata(&dc_wdt_wdd, wdt); 140d3b08185SDamien Riegel watchdog_set_restart_priority(&dc_wdt_wdd, 128); 141336694a0SBaruch Siach watchdog_init_timeout(&dc_wdt_wdd, timeout, dev); 142dd36f6ceSGuenter Roeck watchdog_stop_on_reboot(&dc_wdt_wdd); 143*cdad2697SWolfram Sang return devm_watchdog_register_device(dev, &dc_wdt_wdd); 144336694a0SBaruch Siach } 145336694a0SBaruch Siach 146336694a0SBaruch Siach static const struct of_device_id dc_wdt_of_match[] = { 147336694a0SBaruch Siach { .compatible = "cnxt,cx92755-wdt", }, 148336694a0SBaruch Siach {}, 149336694a0SBaruch Siach }; 150336694a0SBaruch Siach MODULE_DEVICE_TABLE(of, dc_wdt_of_match); 151336694a0SBaruch Siach 152336694a0SBaruch Siach static struct platform_driver dc_wdt_driver = { 153336694a0SBaruch Siach .probe = dc_wdt_probe, 154336694a0SBaruch Siach .driver = { 155336694a0SBaruch Siach .name = "digicolor-wdt", 156336694a0SBaruch Siach .of_match_table = dc_wdt_of_match, 157336694a0SBaruch Siach }, 158336694a0SBaruch Siach }; 159336694a0SBaruch Siach module_platform_driver(dc_wdt_driver); 160336694a0SBaruch Siach 161336694a0SBaruch Siach MODULE_AUTHOR("Baruch Siach <baruch@tkos.co.il>"); 162336694a0SBaruch Siach MODULE_DESCRIPTION("Driver for Conexant Digicolor watchdog timer"); 163336694a0SBaruch Siach MODULE_LICENSE("GPL"); 164