1*8f91fc56SJoshua Henderson /* 2*8f91fc56SJoshua Henderson * PIC32 watchdog driver 3*8f91fc56SJoshua Henderson * 4*8f91fc56SJoshua Henderson * Joshua Henderson <joshua.henderson@microchip.com> 5*8f91fc56SJoshua Henderson * Copyright (c) 2016, Microchip Technology Inc. 6*8f91fc56SJoshua Henderson * 7*8f91fc56SJoshua Henderson * This program is free software; you can redistribute it and/or 8*8f91fc56SJoshua Henderson * modify it under the terms of the GNU General Public License 9*8f91fc56SJoshua Henderson * as published by the Free Software Foundation; either version 10*8f91fc56SJoshua Henderson * 2 of the License, or (at your option) any later version. 11*8f91fc56SJoshua Henderson */ 12*8f91fc56SJoshua Henderson #include <linux/clk.h> 13*8f91fc56SJoshua Henderson #include <linux/device.h> 14*8f91fc56SJoshua Henderson #include <linux/err.h> 15*8f91fc56SJoshua Henderson #include <linux/io.h> 16*8f91fc56SJoshua Henderson #include <linux/kernel.h> 17*8f91fc56SJoshua Henderson #include <linux/module.h> 18*8f91fc56SJoshua Henderson #include <linux/of.h> 19*8f91fc56SJoshua Henderson #include <linux/of_device.h> 20*8f91fc56SJoshua Henderson #include <linux/platform_device.h> 21*8f91fc56SJoshua Henderson #include <linux/pm.h> 22*8f91fc56SJoshua Henderson #include <linux/watchdog.h> 23*8f91fc56SJoshua Henderson 24*8f91fc56SJoshua Henderson #include <asm/mach-pic32/pic32.h> 25*8f91fc56SJoshua Henderson 26*8f91fc56SJoshua Henderson /* Watchdog Timer Registers */ 27*8f91fc56SJoshua Henderson #define WDTCON_REG 0x00 28*8f91fc56SJoshua Henderson 29*8f91fc56SJoshua Henderson /* Watchdog Timer Control Register fields */ 30*8f91fc56SJoshua Henderson #define WDTCON_WIN_EN BIT(0) 31*8f91fc56SJoshua Henderson #define WDTCON_RMCS_MASK 0x0003 32*8f91fc56SJoshua Henderson #define WDTCON_RMCS_SHIFT 0x0006 33*8f91fc56SJoshua Henderson #define WDTCON_RMPS_MASK 0x001F 34*8f91fc56SJoshua Henderson #define WDTCON_RMPS_SHIFT 0x0008 35*8f91fc56SJoshua Henderson #define WDTCON_ON BIT(15) 36*8f91fc56SJoshua Henderson #define WDTCON_CLR_KEY 0x5743 37*8f91fc56SJoshua Henderson 38*8f91fc56SJoshua Henderson /* Reset Control Register fields for watchdog */ 39*8f91fc56SJoshua Henderson #define RESETCON_TIMEOUT_IDLE BIT(2) 40*8f91fc56SJoshua Henderson #define RESETCON_TIMEOUT_SLEEP BIT(3) 41*8f91fc56SJoshua Henderson #define RESETCON_WDT_TIMEOUT BIT(4) 42*8f91fc56SJoshua Henderson 43*8f91fc56SJoshua Henderson struct pic32_wdt { 44*8f91fc56SJoshua Henderson void __iomem *regs; 45*8f91fc56SJoshua Henderson void __iomem *rst_base; 46*8f91fc56SJoshua Henderson struct clk *clk; 47*8f91fc56SJoshua Henderson }; 48*8f91fc56SJoshua Henderson 49*8f91fc56SJoshua Henderson static inline bool pic32_wdt_is_win_enabled(struct pic32_wdt *wdt) 50*8f91fc56SJoshua Henderson { 51*8f91fc56SJoshua Henderson return !!(readl(wdt->regs + WDTCON_REG) & WDTCON_WIN_EN); 52*8f91fc56SJoshua Henderson } 53*8f91fc56SJoshua Henderson 54*8f91fc56SJoshua Henderson static inline u32 pic32_wdt_get_post_scaler(struct pic32_wdt *wdt) 55*8f91fc56SJoshua Henderson { 56*8f91fc56SJoshua Henderson u32 v = readl(wdt->regs + WDTCON_REG); 57*8f91fc56SJoshua Henderson 58*8f91fc56SJoshua Henderson return (v >> WDTCON_RMPS_SHIFT) & WDTCON_RMPS_MASK; 59*8f91fc56SJoshua Henderson } 60*8f91fc56SJoshua Henderson 61*8f91fc56SJoshua Henderson static inline u32 pic32_wdt_get_clk_id(struct pic32_wdt *wdt) 62*8f91fc56SJoshua Henderson { 63*8f91fc56SJoshua Henderson u32 v = readl(wdt->regs + WDTCON_REG); 64*8f91fc56SJoshua Henderson 65*8f91fc56SJoshua Henderson return (v >> WDTCON_RMCS_SHIFT) & WDTCON_RMCS_MASK; 66*8f91fc56SJoshua Henderson } 67*8f91fc56SJoshua Henderson 68*8f91fc56SJoshua Henderson static int pic32_wdt_bootstatus(struct pic32_wdt *wdt) 69*8f91fc56SJoshua Henderson { 70*8f91fc56SJoshua Henderson u32 v = readl(wdt->rst_base); 71*8f91fc56SJoshua Henderson 72*8f91fc56SJoshua Henderson writel(RESETCON_WDT_TIMEOUT, PIC32_CLR(wdt->rst_base)); 73*8f91fc56SJoshua Henderson 74*8f91fc56SJoshua Henderson return v & RESETCON_WDT_TIMEOUT; 75*8f91fc56SJoshua Henderson } 76*8f91fc56SJoshua Henderson 77*8f91fc56SJoshua Henderson static u32 pic32_wdt_get_timeout_secs(struct pic32_wdt *wdt, struct device *dev) 78*8f91fc56SJoshua Henderson { 79*8f91fc56SJoshua Henderson unsigned long rate; 80*8f91fc56SJoshua Henderson u32 period, ps, terminal; 81*8f91fc56SJoshua Henderson 82*8f91fc56SJoshua Henderson rate = clk_get_rate(wdt->clk); 83*8f91fc56SJoshua Henderson 84*8f91fc56SJoshua Henderson dev_dbg(dev, "wdt: clk_id %d, clk_rate %lu (prescale)\n", 85*8f91fc56SJoshua Henderson pic32_wdt_get_clk_id(wdt), rate); 86*8f91fc56SJoshua Henderson 87*8f91fc56SJoshua Henderson /* default, prescaler of 32 (i.e. div-by-32) is implicit. */ 88*8f91fc56SJoshua Henderson rate >>= 5; 89*8f91fc56SJoshua Henderson if (!rate) 90*8f91fc56SJoshua Henderson return 0; 91*8f91fc56SJoshua Henderson 92*8f91fc56SJoshua Henderson /* calculate terminal count from postscaler. */ 93*8f91fc56SJoshua Henderson ps = pic32_wdt_get_post_scaler(wdt); 94*8f91fc56SJoshua Henderson terminal = BIT(ps); 95*8f91fc56SJoshua Henderson 96*8f91fc56SJoshua Henderson /* find time taken (in secs) to reach terminal count */ 97*8f91fc56SJoshua Henderson period = terminal / rate; 98*8f91fc56SJoshua Henderson dev_dbg(dev, 99*8f91fc56SJoshua Henderson "wdt: clk_rate %lu (postscale) / terminal %d, timeout %dsec\n", 100*8f91fc56SJoshua Henderson rate, terminal, period); 101*8f91fc56SJoshua Henderson 102*8f91fc56SJoshua Henderson return period; 103*8f91fc56SJoshua Henderson } 104*8f91fc56SJoshua Henderson 105*8f91fc56SJoshua Henderson static void pic32_wdt_keepalive(struct pic32_wdt *wdt) 106*8f91fc56SJoshua Henderson { 107*8f91fc56SJoshua Henderson /* write key through single half-word */ 108*8f91fc56SJoshua Henderson writew(WDTCON_CLR_KEY, wdt->regs + WDTCON_REG + 2); 109*8f91fc56SJoshua Henderson } 110*8f91fc56SJoshua Henderson 111*8f91fc56SJoshua Henderson static int pic32_wdt_start(struct watchdog_device *wdd) 112*8f91fc56SJoshua Henderson { 113*8f91fc56SJoshua Henderson struct pic32_wdt *wdt = watchdog_get_drvdata(wdd); 114*8f91fc56SJoshua Henderson 115*8f91fc56SJoshua Henderson writel(WDTCON_ON, PIC32_SET(wdt->regs + WDTCON_REG)); 116*8f91fc56SJoshua Henderson pic32_wdt_keepalive(wdt); 117*8f91fc56SJoshua Henderson 118*8f91fc56SJoshua Henderson return 0; 119*8f91fc56SJoshua Henderson } 120*8f91fc56SJoshua Henderson 121*8f91fc56SJoshua Henderson static int pic32_wdt_stop(struct watchdog_device *wdd) 122*8f91fc56SJoshua Henderson { 123*8f91fc56SJoshua Henderson struct pic32_wdt *wdt = watchdog_get_drvdata(wdd); 124*8f91fc56SJoshua Henderson 125*8f91fc56SJoshua Henderson writel(WDTCON_ON, PIC32_CLR(wdt->regs + WDTCON_REG)); 126*8f91fc56SJoshua Henderson 127*8f91fc56SJoshua Henderson /* 128*8f91fc56SJoshua Henderson * Cannot touch registers in the CPU cycle following clearing the 129*8f91fc56SJoshua Henderson * ON bit. 130*8f91fc56SJoshua Henderson */ 131*8f91fc56SJoshua Henderson nop(); 132*8f91fc56SJoshua Henderson 133*8f91fc56SJoshua Henderson return 0; 134*8f91fc56SJoshua Henderson } 135*8f91fc56SJoshua Henderson 136*8f91fc56SJoshua Henderson static int pic32_wdt_ping(struct watchdog_device *wdd) 137*8f91fc56SJoshua Henderson { 138*8f91fc56SJoshua Henderson struct pic32_wdt *wdt = watchdog_get_drvdata(wdd); 139*8f91fc56SJoshua Henderson 140*8f91fc56SJoshua Henderson pic32_wdt_keepalive(wdt); 141*8f91fc56SJoshua Henderson 142*8f91fc56SJoshua Henderson return 0; 143*8f91fc56SJoshua Henderson } 144*8f91fc56SJoshua Henderson 145*8f91fc56SJoshua Henderson static const struct watchdog_ops pic32_wdt_fops = { 146*8f91fc56SJoshua Henderson .owner = THIS_MODULE, 147*8f91fc56SJoshua Henderson .start = pic32_wdt_start, 148*8f91fc56SJoshua Henderson .stop = pic32_wdt_stop, 149*8f91fc56SJoshua Henderson .ping = pic32_wdt_ping, 150*8f91fc56SJoshua Henderson }; 151*8f91fc56SJoshua Henderson 152*8f91fc56SJoshua Henderson static const struct watchdog_info pic32_wdt_ident = { 153*8f91fc56SJoshua Henderson .options = WDIOF_KEEPALIVEPING | 154*8f91fc56SJoshua Henderson WDIOF_MAGICCLOSE | WDIOF_CARDRESET, 155*8f91fc56SJoshua Henderson .identity = "PIC32 Watchdog", 156*8f91fc56SJoshua Henderson }; 157*8f91fc56SJoshua Henderson 158*8f91fc56SJoshua Henderson static struct watchdog_device pic32_wdd = { 159*8f91fc56SJoshua Henderson .info = &pic32_wdt_ident, 160*8f91fc56SJoshua Henderson .ops = &pic32_wdt_fops, 161*8f91fc56SJoshua Henderson }; 162*8f91fc56SJoshua Henderson 163*8f91fc56SJoshua Henderson static const struct of_device_id pic32_wdt_dt_ids[] = { 164*8f91fc56SJoshua Henderson { .compatible = "microchip,pic32mzda-wdt", }, 165*8f91fc56SJoshua Henderson { /* sentinel */ } 166*8f91fc56SJoshua Henderson }; 167*8f91fc56SJoshua Henderson MODULE_DEVICE_TABLE(of, pic32_wdt_dt_ids); 168*8f91fc56SJoshua Henderson 169*8f91fc56SJoshua Henderson static int pic32_wdt_drv_probe(struct platform_device *pdev) 170*8f91fc56SJoshua Henderson { 171*8f91fc56SJoshua Henderson int ret; 172*8f91fc56SJoshua Henderson struct watchdog_device *wdd = &pic32_wdd; 173*8f91fc56SJoshua Henderson struct pic32_wdt *wdt; 174*8f91fc56SJoshua Henderson struct resource *mem; 175*8f91fc56SJoshua Henderson 176*8f91fc56SJoshua Henderson wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); 177*8f91fc56SJoshua Henderson if (IS_ERR(wdt)) 178*8f91fc56SJoshua Henderson return PTR_ERR(wdt); 179*8f91fc56SJoshua Henderson 180*8f91fc56SJoshua Henderson mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); 181*8f91fc56SJoshua Henderson wdt->regs = devm_ioremap_resource(&pdev->dev, mem); 182*8f91fc56SJoshua Henderson if (IS_ERR(wdt->regs)) 183*8f91fc56SJoshua Henderson return PTR_ERR(wdt->regs); 184*8f91fc56SJoshua Henderson 185*8f91fc56SJoshua Henderson wdt->rst_base = devm_ioremap(&pdev->dev, PIC32_BASE_RESET, 0x10); 186*8f91fc56SJoshua Henderson if (IS_ERR(wdt->rst_base)) 187*8f91fc56SJoshua Henderson return PTR_ERR(wdt->rst_base); 188*8f91fc56SJoshua Henderson 189*8f91fc56SJoshua Henderson wdt->clk = devm_clk_get(&pdev->dev, NULL); 190*8f91fc56SJoshua Henderson if (IS_ERR(wdt->clk)) { 191*8f91fc56SJoshua Henderson dev_err(&pdev->dev, "clk not found\n"); 192*8f91fc56SJoshua Henderson return PTR_ERR(wdt->clk); 193*8f91fc56SJoshua Henderson } 194*8f91fc56SJoshua Henderson 195*8f91fc56SJoshua Henderson ret = clk_prepare_enable(wdt->clk); 196*8f91fc56SJoshua Henderson if (ret) { 197*8f91fc56SJoshua Henderson dev_err(&pdev->dev, "clk enable failed\n"); 198*8f91fc56SJoshua Henderson return ret; 199*8f91fc56SJoshua Henderson } 200*8f91fc56SJoshua Henderson 201*8f91fc56SJoshua Henderson if (pic32_wdt_is_win_enabled(wdt)) { 202*8f91fc56SJoshua Henderson dev_err(&pdev->dev, "windowed-clear mode is not supported.\n"); 203*8f91fc56SJoshua Henderson ret = -ENODEV; 204*8f91fc56SJoshua Henderson goto out_disable_clk; 205*8f91fc56SJoshua Henderson } 206*8f91fc56SJoshua Henderson 207*8f91fc56SJoshua Henderson wdd->timeout = pic32_wdt_get_timeout_secs(wdt, &pdev->dev); 208*8f91fc56SJoshua Henderson if (!wdd->timeout) { 209*8f91fc56SJoshua Henderson dev_err(&pdev->dev, 210*8f91fc56SJoshua Henderson "failed to read watchdog register timeout\n"); 211*8f91fc56SJoshua Henderson ret = -EINVAL; 212*8f91fc56SJoshua Henderson goto out_disable_clk; 213*8f91fc56SJoshua Henderson } 214*8f91fc56SJoshua Henderson 215*8f91fc56SJoshua Henderson dev_info(&pdev->dev, "timeout %d\n", wdd->timeout); 216*8f91fc56SJoshua Henderson 217*8f91fc56SJoshua Henderson wdd->bootstatus = pic32_wdt_bootstatus(wdt) ? WDIOF_CARDRESET : 0; 218*8f91fc56SJoshua Henderson 219*8f91fc56SJoshua Henderson watchdog_set_nowayout(wdd, WATCHDOG_NOWAYOUT); 220*8f91fc56SJoshua Henderson watchdog_set_drvdata(wdd, wdt); 221*8f91fc56SJoshua Henderson 222*8f91fc56SJoshua Henderson ret = watchdog_register_device(wdd); 223*8f91fc56SJoshua Henderson if (ret) { 224*8f91fc56SJoshua Henderson dev_err(&pdev->dev, "watchdog register failed, err %d\n", ret); 225*8f91fc56SJoshua Henderson goto out_disable_clk; 226*8f91fc56SJoshua Henderson } 227*8f91fc56SJoshua Henderson 228*8f91fc56SJoshua Henderson platform_set_drvdata(pdev, wdd); 229*8f91fc56SJoshua Henderson 230*8f91fc56SJoshua Henderson return 0; 231*8f91fc56SJoshua Henderson 232*8f91fc56SJoshua Henderson out_disable_clk: 233*8f91fc56SJoshua Henderson clk_disable_unprepare(wdt->clk); 234*8f91fc56SJoshua Henderson 235*8f91fc56SJoshua Henderson return ret; 236*8f91fc56SJoshua Henderson } 237*8f91fc56SJoshua Henderson 238*8f91fc56SJoshua Henderson static int pic32_wdt_drv_remove(struct platform_device *pdev) 239*8f91fc56SJoshua Henderson { 240*8f91fc56SJoshua Henderson struct watchdog_device *wdd = platform_get_drvdata(pdev); 241*8f91fc56SJoshua Henderson struct pic32_wdt *wdt = watchdog_get_drvdata(wdd); 242*8f91fc56SJoshua Henderson 243*8f91fc56SJoshua Henderson watchdog_unregister_device(wdd); 244*8f91fc56SJoshua Henderson clk_disable_unprepare(wdt->clk); 245*8f91fc56SJoshua Henderson 246*8f91fc56SJoshua Henderson return 0; 247*8f91fc56SJoshua Henderson } 248*8f91fc56SJoshua Henderson 249*8f91fc56SJoshua Henderson static struct platform_driver pic32_wdt_driver = { 250*8f91fc56SJoshua Henderson .probe = pic32_wdt_drv_probe, 251*8f91fc56SJoshua Henderson .remove = pic32_wdt_drv_remove, 252*8f91fc56SJoshua Henderson .driver = { 253*8f91fc56SJoshua Henderson .name = "pic32-wdt", 254*8f91fc56SJoshua Henderson .owner = THIS_MODULE, 255*8f91fc56SJoshua Henderson .of_match_table = of_match_ptr(pic32_wdt_dt_ids), 256*8f91fc56SJoshua Henderson } 257*8f91fc56SJoshua Henderson }; 258*8f91fc56SJoshua Henderson 259*8f91fc56SJoshua Henderson module_platform_driver(pic32_wdt_driver); 260*8f91fc56SJoshua Henderson 261*8f91fc56SJoshua Henderson MODULE_AUTHOR("Joshua Henderson <joshua.henderson@microchip.com>"); 262*8f91fc56SJoshua Henderson MODULE_DESCRIPTION("Microchip PIC32 Watchdog Timer"); 263*8f91fc56SJoshua Henderson MODULE_LICENSE("GPL"); 264