/* * <:copyright-BRCM:2016:DUAL/GPL:standard * * Copyright (c) 2016 Broadcom * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, as published by * the Free Software Foundation (the "GPL"). * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * * A copy of the GPL is available at http://www.broadcom.com/licenses/GPLv2.php, or by * writing to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. * * :> */ #ifdef CONFIG_AVM_FASTIRQ #include #define CLIENT_FIQ_PRIO FIQ_PRIO_WATCHDOG #endif #include #include #include #include #include #include #include #include #if defined(CONFIG_BCM_EXT_TIMER) #include "bcm_ext_timer.h" #endif #include "board_wd.h" #if defined(CONFIG_AVM_FASTIRQ) #include #define _BUILD_AVM_CONTEXT_FUNC(func) firq_##func #else #define _BUILD_AVM_CONTEXT_FUNC(func) func #endif #if defined(CONFIG_BCM_WATCHDOG_TIMER) volatile watchdog_cfg watchdog_data = {0, 5000000, 0, 0, 8, 0}; static CB_WDOG_LIST *g_cb_wdog_list_head = NULL; static DEFINE_SPINLOCK(watchdog_spinlock); /* watchdog restart work */ //static struct work_struct watchdogRestartWork; //static int watchdog_restart_in_progress = 0; static void __init kerSysInitWatchdogCBList( void ); #if defined(INTERRUPT_ID_WDTIMER) || !defined(CONFIG_BCM_EXT_TIMER) //static irqreturn_t watchdog_isr(int irq, void *ignore); #else //static void watchdog_isr(int param); #endif #endif #if defined(CONFIG_BCM_WATCHDOG_TIMER) void bcm_acknowledge_watchdog( void ) { unsigned long flags; _BUILD_AVM_CONTEXT_FUNC( spin_lock_irqsave(&watchdog_spinlock, flags) ); if (watchdog_data.userMode) watchdog_data.userTimeout = 0; #if defined(CONFIG_BCM947189) #elif defined (CONFIG_BCM96838) WDTIMER->WD0Ctl = 0xFF00; WDTIMER->WD0Ctl = 0x00FF; #elif defined(CONFIG_BCM96858) || defined (CONFIG_BCM963158) || defined(CONFIG_BCM96846) || defined(CONFIG_BCM96856) || defined(CONFIG_BCM947622) || defined(CONFIG_BCM963178) || defined(CONFIG_BCM96878) WDTIMER0->WatchDogCtl = 0xFF00; WDTIMER0->WatchDogCtl = 0x00FF; #else TIMER->WatchDogCtl = 0xFF00; TIMER->WatchDogCtl = 0x00FF; #endif _BUILD_AVM_CONTEXT_FUNC( spin_unlock_irqrestore(&watchdog_spinlock, flags) ); } int bcm_suspend_watchdog( void ) { unsigned long flags; int needResume = 0; _BUILD_AVM_CONTEXT_FUNC( spin_lock_irqsave(&watchdog_spinlock, flags) ); if (watchdog_data.enabled && !watchdog_data.suspend) { #if defined (CONFIG_BCM96838) WDTIMER->WD0Ctl = 0xEE00; WDTIMER->WD0Ctl = 0x00EE; #elif defined(CONFIG_BCM96858) || defined (CONFIG_BCM963158) || defined(CONFIG_BCM96846) || defined(CONFIG_BCM947622) || defined(CONFIG_BCM963178) || defined(CONFIG_BCM96878) WDTIMER0->WatchDogCtl = 0xEE00; WDTIMER0->WatchDogCtl = 0x00EE; #else TIMER->WatchDogCtl = 0xEE00; TIMER->WatchDogCtl = 0x00EE; #endif watchdog_data.suspend = 1; needResume = 1; } _BUILD_AVM_CONTEXT_FUNC( spin_unlock_irqrestore(&watchdog_spinlock, flags) ); return needResume; } void bcm_resume_watchdog( void ) { unsigned long flags; bcm_acknowledge_watchdog(); spin_lock_irqsave(&watchdog_spinlock, flags); watchdog_data.suspend = 0; spin_unlock_irqrestore(&watchdog_spinlock, flags); } void bcm_intclear_watchdog( void ) { #if !defined(INTERRUPT_ID_WDTIMER) && !defined(CONFIG_BCM_EXT_TIMER) /* clear the interrupt */ TIMER->TimerInts |= WATCHDOG; #endif } void bcm_reset_watchdog(void) { unsigned long flags; spin_lock_irqsave(&watchdog_spinlock, flags); if (watchdog_data.userMode) watchdog_data.userTimeout = 0; spin_unlock_irqrestore(&watchdog_spinlock, flags); return; } static void _bcm_configure_watchdog(unsigned int timer) { unsigned long flags; _BUILD_AVM_CONTEXT_FUNC( spin_lock_irqsave(&watchdog_spinlock, flags) ); timer = min(timer, (UINT_MAX / (FPERIPH_WD/1000000))); #if defined(CONFIG_BCM947189) #elif defined (CONFIG_BCM96838) WDTIMER->WD0Ctl = 0xEE00; WDTIMER->WD0Ctl = 0x00EE; WDTIMER->WD0DefCount = timer * (FPERIPH_WD/1000000); #elif defined(CONFIG_BCM96858) || defined (CONFIG_BCM963158) || defined(CONFIG_BCM96846) || defined(CONFIG_BCM96856) || defined(CONFIG_BCM947622) || defined(CONFIG_BCM963178) || defined(CONFIG_BCM96878) WDTIMER0->WatchDogCtl = 0xEE00; WDTIMER0->WatchDogCtl = 0x00EE; SET_PRE_SCALE_VALUE(WDTIMER0, 1000); SET_PRE_SCALE_ENABLED(WDTIMER0, 1); WDTIMER0->WatchDogDefCount = timer / ((IS_PRE_SCALE_ENABLED(WDTIMER0)) ? (GET_PRE_SCALE_VALUE(WDTIMER0)) : 1) * (FPERIPH_WD/1000000); #else TIMER->WatchDogCtl = 0xEE00; TIMER->WatchDogCtl = 0x00EE; TIMER->WatchDogDefCount = timer * (FPERIPH_WD/1000000); #endif _BUILD_AVM_CONTEXT_FUNC( spin_unlock_irqrestore(&watchdog_spinlock, flags) ); } void bcm_configure_watchdog( unsigned int enabled, unsigned int timer, unsigned int userMode, unsigned int userThreshold ) { unsigned long flags; spin_lock_irqsave(&watchdog_spinlock, flags); watchdog_data.userMode = userMode; watchdog_data.userThreshold = userThreshold * 2; // watchdog interrupt is half of timer watchdog_data.userTimeout = 0; // reset userTimeout spin_unlock_irqrestore(&watchdog_spinlock, flags); _bcm_configure_watchdog(timer); spin_lock_irqsave(&watchdog_spinlock, flags); if (watchdog_data.enabled != enabled) { watchdog_data.timer = timer; if (enabled) { watchdog_data.enabled = enabled; watchdog_data.suspend = 0; spin_unlock_irqrestore(&watchdog_spinlock, flags); start_watchdog(watchdog_data.timer, 0); } else { watchdog_data.enabled = enabled; spin_unlock_irqrestore(&watchdog_spinlock, flags); bcm_suspend_watchdog(); } } else if (watchdog_data.timer != timer) { watchdog_data.timer = timer; if (watchdog_data.enabled) { watchdog_data.suspend = 0; spin_unlock_irqrestore(&watchdog_spinlock, flags); start_watchdog(watchdog_data.timer, 0); } else spin_unlock_irqrestore(&watchdog_spinlock, flags); } else spin_unlock_irqrestore(&watchdog_spinlock, flags); } static void __init kerSysInitWatchdogCBList( void ) { CB_WDOG_LIST *new_node; unsigned long flags; if( g_cb_wdog_list_head != NULL) { printk("Error: kerSysInitWatchdogCBList: list head is not null\n"); return; } new_node= (CB_WDOG_LIST *)kmalloc(sizeof(CB_WDOG_LIST), GFP_KERNEL); memset(new_node, 0x00, sizeof(CB_WDOG_LIST)); INIT_LIST_HEAD(&new_node->list); spin_lock_irqsave(&watchdog_spinlock, flags); g_cb_wdog_list_head = new_node; spin_unlock_irqrestore(&watchdog_spinlock, flags); } void bcm_init_watchdog(void) { kerSysInitWatchdogCBList(); } void kerSysRegisterWatchdogCB(char *devname, void *cbfn, void *context) { CB_WDOG_LIST *new_node; unsigned long flags; // do all the stuff that can be done without the lock first if( devname == NULL || cbfn == NULL ) { printk("Error: kerSysRegisterWatchdogCB: register info not enough (%s,%x,%x)\n", devname, (unsigned int)cbfn, (unsigned int)context); return; } if (strlen(devname) > (IFNAMSIZ - 1)) printk("Warning: kerSysRegisterWatchdogCB: devname too long, will be truncated\n"); new_node= (CB_WDOG_LIST *)kmalloc(sizeof(CB_WDOG_LIST), GFP_KERNEL); memset(new_node, 0x00, sizeof(CB_WDOG_LIST)); INIT_LIST_HEAD(&new_node->list); strncpy(new_node->name, devname, IFNAMSIZ-1); new_node->cb_wd_fn = (cb_watchdog_t)cbfn; new_node->context = context; // OK, now acquire the lock and insert into list spin_lock_irqsave(&watchdog_spinlock, flags); if( g_cb_wdog_list_head == NULL) { printk("Error: kerSysRegisterWatchdogCB: list head is null\n"); kfree(new_node); } else { list_add(&new_node->list, &g_cb_wdog_list_head->list); printk("watchdog: kerSysRegisterWatchdogCB: %s registered \n", devname); } spin_unlock_irqrestore(&watchdog_spinlock, flags); return; } void kerSysDeregisterWatchdogCB(char *devname) { struct list_head *pos; CB_WDOG_LIST *tmp; int found=0; unsigned long flags; if(devname == NULL) { printk("Error: kerSysDeregisterWatchdogCB: devname is null\n"); return; } printk("kerSysDeregisterWatchdogCB: %s is deregistering\n", devname); spin_lock_irqsave(&watchdog_spinlock, flags); if(g_cb_wdog_list_head == NULL) { printk("Error: kerSysDeregisterWatchdogCB: list head is null\n"); } else { list_for_each(pos, &g_cb_wdog_list_head->list) { tmp = list_entry(pos, CB_WDOG_LIST, list); if(!strcmp(tmp->name, devname)) { list_del(pos); kfree(tmp); found = 1; printk("kerSysDeregisterWatchdogCB: %s is deregistered\n", devname); break; } } if (!found) printk("kerSysDeregisterWatchdogCB: %s not (de)registered\n", devname); } spin_unlock_irqrestore(&watchdog_spinlock, flags); return; } #endif void start_watchdog(unsigned int timer, unsigned int reset) { #if defined(CONFIG_BCM_WATCHDOG_TIMER) unsigned long flags; /* if watch dog is disabled and reset is 0, do nothing */ if (!reset && !watchdog_data.enabled) { return; } if (timer != watchdog_data.timer) { _bcm_configure_watchdog(timer); } bcm_acknowledge_watchdog(); spin_lock_irqsave(&watchdog_spinlock, flags); /* when reset is 1, disable interrupt */ if (reset) { /* reset userTimeout value */ if (watchdog_data.userMode) watchdog_data.userTimeout = 0; } spin_unlock_irqrestore(&watchdog_spinlock, flags); #endif /* defined(CONFIG_BCM_WATCHDOG_TIMER)*/ } #if defined(CONFIG_BCM960333) void disablePLCWatchdog(void) { unsigned int *pReg; pReg = (unsigned int *) ioremap_nocache(PLC_STATUS_ADDR, 4); if (pReg != NULL) { *pReg = PLC_STATUS_RUNNING_WDOG_DISABLED; iounmap(pReg); } } #endif /*CONFIG_BCM960333*/ void board_wd_init(void) { #if defined(CONFIG_BCM_WATCHDOG_TIMER) //kerSysInitWatchdogCBList(); #endif return; }