/* Copyright (c) 2010-2014 The Linux Foundation. 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 and * only version 2 as published by the Free Software Foundation. * * 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. */ #include #include #include #include #include #include #include #include #include #include static void __iomem *wdt; #define WDT_HZ 32768 #define WDT0_RST 0x38 #define WDT0_EN 0x40 #define WDT0_STS 0x44 #define WDT0_BARK_TIME 0x4C #define WDT0_BITE_TIME 0x5C #if IS_ENABLED(CONFIG_ARCH_IPQ806X_DT) extern struct smp_operations qcom_smp_kpssv1_ops; #endif static void wdt_restart(char str __attribute__ ((unused)), const char *cmd __attribute__ ((unused))) { writel_relaxed(1, wdt + WDT0_RST); writel_relaxed(5*0x31F3, wdt + WDT0_BARK_TIME); writel_relaxed(0x31F3, wdt + WDT0_BITE_TIME); writel_relaxed(1, wdt + WDT0_EN); } #if defined(CONFIG_AVM_ENHANCED) #define GIC_PPI_START 16 #define WDT0_ACCSCSSNBARK_INT (GIC_PPI_START + 4) struct _wdt_pdata { unsigned int dummy; }; static struct wdt_pdata __percpu **percpu_pdata; /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static irqreturn_t wdt_bark_handler(int irq, void *dev_id) { struct pt_regs *regs = get_irq_regs(); flush_cache_all(); #if defined(CONFIG_SMP) bust_spinlocks(1); /*--- oops_in_progress ---*/ pr_emerg("HardwareWatchDog - NMI taken - at first stop other CPU's\n"); smp_send_stop(); #endif/*--- #if defined(CONFIG_SMP) ---*/ die("HardwareWatchDog - NMI taken" , regs, 0); return IRQ_HANDLED; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static int wdt_install_irq(int wdt_irq_nmb) { int ret; if(percpu_pdata) { return 0; } percpu_pdata = (struct wdt_pdata __percpu **)alloc_percpu(struct _wdt_pdata *); if(percpu_pdata == NULL) { pr_err("%s: memory allocation failed for percpu data\n", __func__); return -ENOMEM; } ret = request_percpu_irq(wdt_irq_nmb, wdt_bark_handler, "wdt_bark", percpu_pdata); if (ret) { pr_err("%s: request_percpu_irq failed\n", __func__); free_percpu(percpu_pdata); percpu_pdata = NULL; return ret; } enable_percpu_irq(wdt_irq_nmb, IRQ_TYPE_EDGE_RISING); return 0; } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static void wdt_setup(unsigned int sec, unsigned int enable) { unsigned int bark_time = 1; unsigned long timeout = sec * WDT_HZ; /*--- pr_info(KERN_ERR"%s: %u s %sable (timeout=%lu)\n", __func__, sec, enable ? "en" : "dis", timeout); ---*/ writel_relaxed(1, wdt + WDT0_RST); mb(); if(enable == 0) { writel_relaxed(0, wdt + WDT0_EN); mb(); return; } if(wdt_install_irq(WDT0_ACCSCSSNBARK_INT)) { bark_time = 3; /*--- don't use wdt-bark-irq ---*/ } writel_relaxed(timeout * bark_time, wdt + WDT0_BARK_TIME); writel_relaxed(timeout * 2, wdt + WDT0_BITE_TIME); writel_relaxed(1, wdt + WDT0_EN); mb(); } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static void wdt_trigger(void) { #if 0 unsigned int slack; slack = readl_relaxed(wdt + WDT0_STS) >> 3; pr_info("%s: trigger before =%u.%03u s\n", __func__, slack / WDT_HZ, ((slack * 1000) / WDT_HZ) % 1000); #endif writel_relaxed(1, wdt + WDT0_RST); mb(); } /*--------------------------------------------------------------------------------*\ * wdt-functions for avm_new \*--------------------------------------------------------------------------------*/ void ar7wdt_hw_init(void) { wdt_setup(25 /* sec */, 1); } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ void ar7wdt_hw_deinit(void) { wdt_setup(0, 0); } /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ void ar7wdt_hw_trigger(void) { wdt_trigger(); } #endif/*--- #if defined(CONFIG_AVM_ENHANCED) ---*/ static int wdt_init(void) { const unsigned long wdt_base = 0x0200A000; const unsigned long wdt_base_len = 4096; wdt = ioremap(wdt_base, wdt_base_len); if (!wdt) { pr_err("QCA watchdog: unable to remap base address 0x%08lx\n", wdt_base); return -EINVAL; } pr_info("QCA watchdog: mapped watchdog mem i/o <0x%08lx-0x%08lx> to " "<0x%08lx-0x%08lx>\n", wdt_base, wdt_base + wdt_base_len - 1, (unsigned long)wdt, (unsigned long)wdt + wdt_base_len - 1); arm_pm_restart = wdt_restart; pr_info("QCA watchdog: setup reboot handler\n"); return 0; } arch_initcall(wdt_init); /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ unsigned int avm_reset_status(void) { #warning dummy avm_reset_status() pr_warn("[%s] not implemented\n", __func__); return 42; } EXPORT_SYMBOL(avm_reset_status); void set_reboot_status_to_Update(void) { #warning dummy set_reboot_status_to_Update() pr_warn("[%s] not implemented\n", __func__); } EXPORT_SYMBOL(set_reboot_status_to_Update); /*--------------------------------------------------------------------------------*\ \*--------------------------------------------------------------------------------*/ static const char * const qcom_dt_match[] __initconst = { "qcom,apq8064", "qcom,apq8074-dragonboard", "qcom,apq8084", "qcom,ipq8062", "qcom,ipq8064", "qcom,msm8660-surf", "qcom,msm8960-cdp", NULL }; DT_MACHINE_START(QCOM_DT, "Qualcomm (Flattened Device Tree)") .dt_compat = qcom_dt_match, #if IS_ENABLED(CONFIG_ARCH_IPQ806X_DT) .smp = smp_ops(qcom_smp_kpssv1_ops), #endif MACHINE_END