1*87a1ef80SDavid Cohen /* 2*87a1ef80SDavid Cohen * intel-mid_wdt: generic Intel MID SCU watchdog driver 3*87a1ef80SDavid Cohen * 4*87a1ef80SDavid Cohen * Platforms supported so far: 5*87a1ef80SDavid Cohen * - Merrifield only 6*87a1ef80SDavid Cohen * 7*87a1ef80SDavid Cohen * Copyright (C) 2014 Intel Corporation. All rights reserved. 8*87a1ef80SDavid Cohen * Contact: David Cohen <david.a.cohen@linux.intel.com> 9*87a1ef80SDavid Cohen * 10*87a1ef80SDavid Cohen * This program is free software; you can redistribute it and/or 11*87a1ef80SDavid Cohen * modify it under the terms of version 2 of the GNU General 12*87a1ef80SDavid Cohen * Public License as published by the Free Software Foundation. 13*87a1ef80SDavid Cohen */ 14*87a1ef80SDavid Cohen 15*87a1ef80SDavid Cohen #include <linux/interrupt.h> 16*87a1ef80SDavid Cohen #include <linux/module.h> 17*87a1ef80SDavid Cohen #include <linux/nmi.h> 18*87a1ef80SDavid Cohen #include <linux/platform_device.h> 19*87a1ef80SDavid Cohen #include <linux/watchdog.h> 20*87a1ef80SDavid Cohen #include <linux/platform_data/intel-mid_wdt.h> 21*87a1ef80SDavid Cohen 22*87a1ef80SDavid Cohen #include <asm/intel_scu_ipc.h> 23*87a1ef80SDavid Cohen #include <asm/intel-mid.h> 24*87a1ef80SDavid Cohen 25*87a1ef80SDavid Cohen #define IPC_WATCHDOG 0xf8 26*87a1ef80SDavid Cohen 27*87a1ef80SDavid Cohen #define MID_WDT_PRETIMEOUT 15 28*87a1ef80SDavid Cohen #define MID_WDT_TIMEOUT_MIN (1 + MID_WDT_PRETIMEOUT) 29*87a1ef80SDavid Cohen #define MID_WDT_TIMEOUT_MAX 170 30*87a1ef80SDavid Cohen #define MID_WDT_DEFAULT_TIMEOUT 90 31*87a1ef80SDavid Cohen 32*87a1ef80SDavid Cohen /* SCU watchdog messages */ 33*87a1ef80SDavid Cohen enum { 34*87a1ef80SDavid Cohen SCU_WATCHDOG_START = 0, 35*87a1ef80SDavid Cohen SCU_WATCHDOG_STOP, 36*87a1ef80SDavid Cohen SCU_WATCHDOG_KEEPALIVE, 37*87a1ef80SDavid Cohen }; 38*87a1ef80SDavid Cohen 39*87a1ef80SDavid Cohen static inline int wdt_command(int sub, u32 *in, int inlen) 40*87a1ef80SDavid Cohen { 41*87a1ef80SDavid Cohen return intel_scu_ipc_command(IPC_WATCHDOG, sub, in, inlen, NULL, 0); 42*87a1ef80SDavid Cohen } 43*87a1ef80SDavid Cohen 44*87a1ef80SDavid Cohen static int wdt_start(struct watchdog_device *wd) 45*87a1ef80SDavid Cohen { 46*87a1ef80SDavid Cohen int ret, in_size; 47*87a1ef80SDavid Cohen int timeout = wd->timeout; 48*87a1ef80SDavid Cohen struct ipc_wd_start { 49*87a1ef80SDavid Cohen u32 pretimeout; 50*87a1ef80SDavid Cohen u32 timeout; 51*87a1ef80SDavid Cohen } ipc_wd_start = { timeout - MID_WDT_PRETIMEOUT, timeout }; 52*87a1ef80SDavid Cohen 53*87a1ef80SDavid Cohen /* 54*87a1ef80SDavid Cohen * SCU expects the input size for watchdog IPC to 55*87a1ef80SDavid Cohen * be based on 4 bytes 56*87a1ef80SDavid Cohen */ 57*87a1ef80SDavid Cohen in_size = DIV_ROUND_UP(sizeof(ipc_wd_start), 4); 58*87a1ef80SDavid Cohen 59*87a1ef80SDavid Cohen ret = wdt_command(SCU_WATCHDOG_START, (u32 *)&ipc_wd_start, in_size); 60*87a1ef80SDavid Cohen if (ret) { 61*87a1ef80SDavid Cohen struct device *dev = watchdog_get_drvdata(wd); 62*87a1ef80SDavid Cohen dev_crit(dev, "error starting watchdog: %d\n", ret); 63*87a1ef80SDavid Cohen } 64*87a1ef80SDavid Cohen 65*87a1ef80SDavid Cohen return ret; 66*87a1ef80SDavid Cohen } 67*87a1ef80SDavid Cohen 68*87a1ef80SDavid Cohen static int wdt_ping(struct watchdog_device *wd) 69*87a1ef80SDavid Cohen { 70*87a1ef80SDavid Cohen int ret; 71*87a1ef80SDavid Cohen 72*87a1ef80SDavid Cohen ret = wdt_command(SCU_WATCHDOG_KEEPALIVE, NULL, 0); 73*87a1ef80SDavid Cohen if (ret) { 74*87a1ef80SDavid Cohen struct device *dev = watchdog_get_drvdata(wd); 75*87a1ef80SDavid Cohen dev_crit(dev, "Error executing keepalive: 0x%x\n", ret); 76*87a1ef80SDavid Cohen } 77*87a1ef80SDavid Cohen 78*87a1ef80SDavid Cohen return ret; 79*87a1ef80SDavid Cohen } 80*87a1ef80SDavid Cohen 81*87a1ef80SDavid Cohen static int wdt_stop(struct watchdog_device *wd) 82*87a1ef80SDavid Cohen { 83*87a1ef80SDavid Cohen int ret; 84*87a1ef80SDavid Cohen 85*87a1ef80SDavid Cohen ret = wdt_command(SCU_WATCHDOG_STOP, NULL, 0); 86*87a1ef80SDavid Cohen if (ret) { 87*87a1ef80SDavid Cohen struct device *dev = watchdog_get_drvdata(wd); 88*87a1ef80SDavid Cohen dev_crit(dev, "Error stopping watchdog: 0x%x\n", ret); 89*87a1ef80SDavid Cohen } 90*87a1ef80SDavid Cohen 91*87a1ef80SDavid Cohen return ret; 92*87a1ef80SDavid Cohen } 93*87a1ef80SDavid Cohen 94*87a1ef80SDavid Cohen static irqreturn_t mid_wdt_irq(int irq, void *dev_id) 95*87a1ef80SDavid Cohen { 96*87a1ef80SDavid Cohen panic("Kernel Watchdog"); 97*87a1ef80SDavid Cohen 98*87a1ef80SDavid Cohen /* This code should not be reached */ 99*87a1ef80SDavid Cohen return IRQ_HANDLED; 100*87a1ef80SDavid Cohen } 101*87a1ef80SDavid Cohen 102*87a1ef80SDavid Cohen static const struct watchdog_info mid_wdt_info = { 103*87a1ef80SDavid Cohen .identity = "Intel MID SCU watchdog", 104*87a1ef80SDavid Cohen .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT, 105*87a1ef80SDavid Cohen }; 106*87a1ef80SDavid Cohen 107*87a1ef80SDavid Cohen static const struct watchdog_ops mid_wdt_ops = { 108*87a1ef80SDavid Cohen .owner = THIS_MODULE, 109*87a1ef80SDavid Cohen .start = wdt_start, 110*87a1ef80SDavid Cohen .stop = wdt_stop, 111*87a1ef80SDavid Cohen .ping = wdt_ping, 112*87a1ef80SDavid Cohen }; 113*87a1ef80SDavid Cohen 114*87a1ef80SDavid Cohen static int mid_wdt_probe(struct platform_device *pdev) 115*87a1ef80SDavid Cohen { 116*87a1ef80SDavid Cohen struct watchdog_device *wdt_dev; 117*87a1ef80SDavid Cohen struct intel_mid_wdt_pdata *pdata = pdev->dev.platform_data; 118*87a1ef80SDavid Cohen int ret; 119*87a1ef80SDavid Cohen 120*87a1ef80SDavid Cohen if (!pdata) { 121*87a1ef80SDavid Cohen dev_err(&pdev->dev, "missing platform data\n"); 122*87a1ef80SDavid Cohen return -EINVAL; 123*87a1ef80SDavid Cohen } 124*87a1ef80SDavid Cohen 125*87a1ef80SDavid Cohen if (pdata->probe) { 126*87a1ef80SDavid Cohen ret = pdata->probe(pdev); 127*87a1ef80SDavid Cohen if (ret) 128*87a1ef80SDavid Cohen return ret; 129*87a1ef80SDavid Cohen } 130*87a1ef80SDavid Cohen 131*87a1ef80SDavid Cohen wdt_dev = devm_kzalloc(&pdev->dev, sizeof(*wdt_dev), GFP_KERNEL); 132*87a1ef80SDavid Cohen if (!wdt_dev) 133*87a1ef80SDavid Cohen return -ENOMEM; 134*87a1ef80SDavid Cohen 135*87a1ef80SDavid Cohen wdt_dev->info = &mid_wdt_info; 136*87a1ef80SDavid Cohen wdt_dev->ops = &mid_wdt_ops; 137*87a1ef80SDavid Cohen wdt_dev->min_timeout = MID_WDT_TIMEOUT_MIN; 138*87a1ef80SDavid Cohen wdt_dev->max_timeout = MID_WDT_TIMEOUT_MAX; 139*87a1ef80SDavid Cohen wdt_dev->timeout = MID_WDT_DEFAULT_TIMEOUT; 140*87a1ef80SDavid Cohen 141*87a1ef80SDavid Cohen watchdog_set_drvdata(wdt_dev, &pdev->dev); 142*87a1ef80SDavid Cohen platform_set_drvdata(pdev, wdt_dev); 143*87a1ef80SDavid Cohen 144*87a1ef80SDavid Cohen ret = devm_request_irq(&pdev->dev, pdata->irq, mid_wdt_irq, 145*87a1ef80SDavid Cohen IRQF_SHARED | IRQF_NO_SUSPEND, "watchdog", 146*87a1ef80SDavid Cohen wdt_dev); 147*87a1ef80SDavid Cohen if (ret) { 148*87a1ef80SDavid Cohen dev_err(&pdev->dev, "error requesting warning irq %d\n", 149*87a1ef80SDavid Cohen pdata->irq); 150*87a1ef80SDavid Cohen return ret; 151*87a1ef80SDavid Cohen } 152*87a1ef80SDavid Cohen 153*87a1ef80SDavid Cohen ret = watchdog_register_device(wdt_dev); 154*87a1ef80SDavid Cohen if (ret) { 155*87a1ef80SDavid Cohen dev_err(&pdev->dev, "error registering watchdog device\n"); 156*87a1ef80SDavid Cohen return ret; 157*87a1ef80SDavid Cohen } 158*87a1ef80SDavid Cohen 159*87a1ef80SDavid Cohen dev_info(&pdev->dev, "Intel MID watchdog device probed\n"); 160*87a1ef80SDavid Cohen 161*87a1ef80SDavid Cohen return 0; 162*87a1ef80SDavid Cohen } 163*87a1ef80SDavid Cohen 164*87a1ef80SDavid Cohen static int mid_wdt_remove(struct platform_device *pdev) 165*87a1ef80SDavid Cohen { 166*87a1ef80SDavid Cohen struct watchdog_device *wd = platform_get_drvdata(pdev); 167*87a1ef80SDavid Cohen watchdog_unregister_device(wd); 168*87a1ef80SDavid Cohen return 0; 169*87a1ef80SDavid Cohen } 170*87a1ef80SDavid Cohen 171*87a1ef80SDavid Cohen static struct platform_driver mid_wdt_driver = { 172*87a1ef80SDavid Cohen .probe = mid_wdt_probe, 173*87a1ef80SDavid Cohen .remove = mid_wdt_remove, 174*87a1ef80SDavid Cohen .driver = { 175*87a1ef80SDavid Cohen .owner = THIS_MODULE, 176*87a1ef80SDavid Cohen .name = "intel_mid_wdt", 177*87a1ef80SDavid Cohen }, 178*87a1ef80SDavid Cohen }; 179*87a1ef80SDavid Cohen 180*87a1ef80SDavid Cohen module_platform_driver(mid_wdt_driver); 181*87a1ef80SDavid Cohen 182*87a1ef80SDavid Cohen MODULE_AUTHOR("David Cohen <david.a.cohen@linux.intel.com>"); 183*87a1ef80SDavid Cohen MODULE_DESCRIPTION("Watchdog Driver for Intel MID platform"); 184*87a1ef80SDavid Cohen MODULE_LICENSE("GPL"); 185