From cb79e737aefe7d517d03e240a657e864b2a37235 Mon Sep 17 00:00:00 2001 From: Prakruthi Deepak Heragu Date: Thu, 8 Aug 2019 14:38:34 -0700 Subject: [PATCH] soc: qcom: Add snapshot of watchdog driver This is a snapshot of watchdog driver as of msm-4.19 commit <5a4fd734e3e0e76c8e0819c47eb0d333e09d0f9f>. (Merge "msm: audio: support for gapless_pcm"). Change-Id: I8b683c8b5c4c56f328d0025b2829f42105d03940 Signed-off-by: Prakruthi Deepak Heragu --- drivers/soc/qcom/Kconfig | 9 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/qcom_watchdog.c | 754 +++++++++++++++++++++++++++++++ include/soc/qcom/watchdog.h | 15 + 4 files changed, 779 insertions(+) create mode 100644 drivers/soc/qcom/qcom_watchdog.c create mode 100644 include/soc/qcom/watchdog.h diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 33dbe71d00c8..ba2bf54140c2 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -558,4 +558,13 @@ config QTEE_SHM_BRIDGE memory sub-allocation and free from the default kernel bridge created by bridge driver. +config QCOM_WATCHDOG + tristate "Qualcomm Watchdog Support" + depends on ARCH_QCOM + help + This enables the watchdog module. It causes kernel panic if the + watchdog times out. It allows for detection of cpu hangs and + deadlocks. It does not run during the bootup process, so it will + not catch any early lockups. + endmenu diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index e17ee51c7a14..f4a6c43c4b3e 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -57,3 +57,4 @@ obj-$(CONFIG_MSM_IDLE_STATS) += lpm-stats.o obj-$(CONFIG_QTI_RPM_STATS_LOG) += rpmh_master_stat.o obj-$(CONFIG_QTEE_SHM_BRIDGE) += qtee_shmbridge.o obj-$(CONFIG_QPNP_PBS) += qpnp-pbs.o +obj-$(CONFIG_QCOM_WATCHDOG) += qcom_watchdog.o diff --git a/drivers/soc/qcom/qcom_watchdog.c b/drivers/soc/qcom/qcom_watchdog.c new file mode 100644 index 000000000000..06310e3086ce --- /dev/null +++ b/drivers/soc/qcom/qcom_watchdog.c @@ -0,0 +1,754 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define WDT0_ACCSCSSNBARK_INT 0 +#define TCSR_WDT_CFG 0x30 +#define WDT0_RST 0x04 +#define WDT0_EN 0x08 +#define WDT0_STS 0x0C +#define WDT0_BARK_TIME 0x10 +#define WDT0_BITE_TIME 0x14 + +#define WDOG_ABSENT 0 + +#define EN 0 +#define UNMASKED_INT_EN 1 + +#define MASK_SIZE 32 +#define SCM_SET_REGSAVE_CMD 0x2 +#define SCM_SVC_SEC_WDOG_DIS 0x7 +#define MAX_CPU_CTX_SIZE 2048 +#define WDT_HZ 32765 + +static struct msm_watchdog_data *wdog_data; + +/* + * user_pet_enable: + * Require userspace to write to a sysfs file every pet_time milliseconds. + * Disabled by default on boot. + */ +struct msm_watchdog_data { + unsigned int __iomem phys_base; + size_t size; + void __iomem *base; + struct device *dev; + unsigned int pet_time; + unsigned int bark_time; + unsigned int bark_irq; + unsigned int bite_irq; + bool do_ipi_ping; + bool wakeup_irq_enable; + unsigned long long last_pet; + unsigned int min_slack_ticks; + unsigned long long min_slack_ns; + void *scm_regsave; + cpumask_t alive_mask; + struct mutex disable_lock; + bool irq_ppi; + struct msm_watchdog_data __percpu **wdog_cpu_dd; + struct notifier_block panic_blk; + + bool enabled; + bool user_pet_enabled; + + struct task_struct *watchdog_task; + struct timer_list pet_timer; + wait_queue_head_t pet_complete; + + bool timer_expired; + bool user_pet_complete; + unsigned long long timer_fired; + unsigned long long thread_start; + unsigned long long ping_start[NR_CPUS]; + unsigned long long ping_end[NR_CPUS]; + int cpu_idle_pc_state[NR_CPUS]; +}; + +/* + * On the kernel command line specify + * qcom_watchdog.enable=1 to enable the watchdog + * By default watchdog is turned on + */ +static int enable = 1; +module_param(enable, int, 0000); + +/* + * Watchdog ipi optimization: + * Does not ping cores in low power mode at pet time to save power. + * This feature is enabled by default. + * + * Can be turned off, by enabling CONFIG_QCOM_WDOG_IPI_ENABLE. + */ +#ifdef CONFIG_QCOM_WDOG_IPI_ENABLE +#define IPI_CORES_IN_LPM 1 +#else +#define IPI_CORES_IN_LPM 0 +#endif + +static void dump_cpu_alive_mask(struct msm_watchdog_data *wdog_dd) +{ + static char alive_mask_buf[MASK_SIZE]; + + scnprintf(alive_mask_buf, MASK_SIZE, "%*pb1", cpumask_pr_args( + &wdog_dd->alive_mask)); + dev_info(wdog_dd->dev, "cpu alive mask from last pet %s\n", + alive_mask_buf); +} + +#ifdef CONFIG_PM_SLEEP +static int msm_watchdog_suspend(struct device *dev) +{ + struct msm_watchdog_data *wdog_dd = dev_get_drvdata(dev); + if (!enable) + return 0; + __raw_writel(1, wdog_dd->base + WDT0_RST); + if (wdog_dd->wakeup_irq_enable) { + /* Make sure register write is complete before proceeding */ + mb(); + wdog_dd->last_pet = sched_clock(); + return 0; + } + __raw_writel(0, wdog_dd->base + WDT0_EN); + /* Make sure watchdog is suspended before setting enable */ + mb(); + wdog_dd->enabled = false; + wdog_dd->last_pet = sched_clock(); + return 0; +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int msm_watchdog_resume(struct device *dev) +{ + struct msm_watchdog_data *wdog_dd = dev_get_drvdata(dev); + if (!enable) + return 0; + if (wdog_dd->wakeup_irq_enable) { + __raw_writel(1, wdog_dd->base + WDT0_RST); + /* Make sure register write is complete before proceeding */ + mb(); + wdog_dd->last_pet = sched_clock(); + return 0; + } + __raw_writel(1, wdog_dd->base + WDT0_EN); + __raw_writel(1, wdog_dd->base + WDT0_RST); + /* Make sure watchdog is reset before setting enable */ + mb(); + wdog_dd->enabled = true; + wdog_dd->last_pet = sched_clock(); + return 0; +} +#endif + +static int panic_wdog_handler(struct notifier_block *this, + unsigned long event, void *ptr) +{ + struct msm_watchdog_data *wdog_dd = container_of(this, + struct msm_watchdog_data, panic_blk); + if (panic_timeout == 0) { + __raw_writel(0, wdog_dd->base + WDT0_EN); + /* Make sure watchdog is enabled before notifying the caller */ + mb(); + } else { + __raw_writel(WDT_HZ * (panic_timeout + 10), + wdog_dd->base + WDT0_BARK_TIME); + __raw_writel(WDT_HZ * (panic_timeout + 10), + wdog_dd->base + WDT0_BITE_TIME); + __raw_writel(1, wdog_dd->base + WDT0_RST); + } + return NOTIFY_DONE; +} + +static void wdog_disable(struct msm_watchdog_data *wdog_dd) +{ + __raw_writel(0, wdog_dd->base + WDT0_EN); + /* Make sure watchdog is disabled before proceeding */ + mb(); + if (wdog_dd->irq_ppi) { + disable_percpu_irq(wdog_dd->bark_irq); + free_percpu_irq(wdog_dd->bark_irq, wdog_dd->wdog_cpu_dd); + } else + devm_free_irq(wdog_dd->dev, wdog_dd->bark_irq, wdog_dd); + enable = 0; + /*Ensure all cpus see update to enable*/ + smp_mb(); + atomic_notifier_chain_unregister(&panic_notifier_list, + &wdog_dd->panic_blk); + del_timer_sync(&wdog_dd->pet_timer); + /* may be suspended after the first write above */ + __raw_writel(0, wdog_dd->base + WDT0_EN); + /* Make sure watchdog is disabled before setting enable */ + mb(); + wdog_dd->enabled = false; + dev_err(wdog_dd->dev, "MSM Apps Watchdog deactivated\n"); +} + +static ssize_t wdog_disable_get(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct msm_watchdog_data *wdog_dd = dev_get_drvdata(dev); + int ret; + + mutex_lock(&wdog_dd->disable_lock); + ret = scnprintf(buf, PAGE_SIZE, "%d\n", enable == 0 ? 1 : 0); + mutex_unlock(&wdog_dd->disable_lock); + return ret; +} + +static ssize_t wdog_disable_set(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct msm_watchdog_data *wdog_dd = dev_get_drvdata(dev); + struct scm_desc desc = {0}; + u8 disable; + int ret; + + ret = kstrtou8(buf, 10, &disable); + if (ret) { + dev_err(wdog_dd->dev, "invalid user input\n"); + return ret; + } + if (disable == 1) { + mutex_lock(&wdog_dd->disable_lock); + if (enable == 0) { + dev_err(wdog_dd->dev, "MSM Apps Watchdog already disabled\n"); + mutex_unlock(&wdog_dd->disable_lock); + return count; + } + disable = 1; + + desc.args[0] = 1; + desc.arginfo = SCM_ARGS(1); + ret = scm_call2(SCM_SIP_FNID(SCM_SVC_BOOT, + SCM_SVC_SEC_WDOG_DIS), &desc); + if (ret) { + dev_err(wdog_dd->dev, + "Failed to deactivate secure wdog\n"); + mutex_unlock(&wdog_dd->disable_lock); + return -EIO; + } + wdog_disable(wdog_dd); + mutex_unlock(&wdog_dd->disable_lock); + } else { + pr_err("invalid operation, only disable = 1 supported\n"); + return -EINVAL; + } + return count; +} + +static DEVICE_ATTR(disable, 0600, wdog_disable_get, wdog_disable_set); + +/* + * Userspace Watchdog Support: + * Write 1 to the "user_pet_enabled" file to enable hw support for a + * userspace watchdog. + * Userspace is required to pet the watchdog by continuing to write 1 + * to this file in the expected interval. + * Userspace may disable this requirement by writing 0 to this same + * file. + */ +static void __wdog_user_pet(struct msm_watchdog_data *wdog_dd) +{ + wdog_dd->user_pet_complete = true; + wake_up(&wdog_dd->pet_complete); +} + +static ssize_t wdog_user_pet_enabled_get(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct msm_watchdog_data *wdog_dd = dev_get_drvdata(dev); + int ret; + + ret = scnprintf(buf, PAGE_SIZE, "%d\n", + wdog_dd->user_pet_enabled); + return ret; +} + +static ssize_t wdog_user_pet_enabled_set(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct msm_watchdog_data *wdog_dd = dev_get_drvdata(dev); + int ret; + + ret = strtobool(buf, &wdog_dd->user_pet_enabled); + if (ret) { + dev_err(wdog_dd->dev, "invalid user input\n"); + return ret; + } + + __wdog_user_pet(wdog_dd); + + return count; +} + +static DEVICE_ATTR(user_pet_enabled, 0600, wdog_user_pet_enabled_get, + wdog_user_pet_enabled_set); + +static ssize_t wdog_pet_time_get(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct msm_watchdog_data *wdog_dd = dev_get_drvdata(dev); + int ret; + + ret = scnprintf(buf, PAGE_SIZE, "%d\n", wdog_dd->pet_time); + return ret; +} + +static DEVICE_ATTR(pet_time, 0400, wdog_pet_time_get, NULL); + +static void pet_watchdog(struct msm_watchdog_data *wdog_dd) +{ + int slack, i, count, prev_count = 0; + unsigned long long bark_time_ns = wdog_dd->bark_time * 1000000ULL; + unsigned long long slack_ns; + unsigned long long time_ns; + + for (i = 0; i < 2; i++) { + count = + (__raw_readl(wdog_dd->base + WDT0_STS) >> 1) & 0xFFFFF; + if (count != prev_count) { + prev_count = count; + i = 0; + } + } + slack = ((wdog_dd->bark_time * WDT_HZ) / 1000) - count; + if (slack < wdog_dd->min_slack_ticks) + wdog_dd->min_slack_ticks = slack; + __raw_writel(1, wdog_dd->base + WDT0_RST); + time_ns = sched_clock(); + slack_ns = (wdog_dd->last_pet + bark_time_ns) - time_ns; + if (slack_ns < wdog_dd->min_slack_ns) + wdog_dd->min_slack_ns = slack_ns; + wdog_dd->last_pet = time_ns; +} + +static void keep_alive_response(void *info) +{ + struct msm_watchdog_data *wdog_dd = info; + int cpu = smp_processor_id(); + + cpumask_set_cpu(cpu, &wdog_dd->alive_mask); + wdog_dd->ping_end[cpu] = sched_clock(); + /* Make sure alive mask is cleared and set in order */ + smp_mb(); +} + +/* + * If this function does not return, it implies one of the + * other cpu's is not responsive. + */ +static void ping_other_cpus(struct msm_watchdog_data *wdog_dd) +{ + int cpu; + + cpumask_clear(&wdog_dd->alive_mask); + /* Make sure alive mask is cleared and set in order */ + smp_mb(); + for_each_cpu(cpu, cpu_online_mask) { + if (!wdog_data->cpu_idle_pc_state[cpu]) { + wdog_dd->ping_start[cpu] = sched_clock(); + smp_call_function_single(cpu, keep_alive_response, + wdog_dd, 1); + } + } +} + +static void pet_task_wakeup(struct timer_list *t) +{ + struct msm_watchdog_data *wdog_dd = + from_timer(wdog_dd, t, pet_timer); + wdog_dd->timer_expired = true; + wdog_dd->timer_fired = sched_clock(); + wake_up(&wdog_dd->pet_complete); +} + +static __ref int watchdog_kthread(void *arg) +{ + struct msm_watchdog_data *wdog_dd = arg; + unsigned long delay_time = 0; + struct sched_param param = {.sched_priority = MAX_RT_PRIO-1}; + int ret, cpu; + + sched_setscheduler(current, SCHED_FIFO, ¶m); + while (!kthread_should_stop()) { + do { + ret = wait_event_interruptible(wdog_dd->pet_complete, + wdog_dd->timer_expired); + } while (ret != 0); + + wdog_dd->thread_start = sched_clock(); + for_each_cpu(cpu, cpu_present_mask) + wdog_dd->ping_start[cpu] = wdog_dd->ping_end[cpu] = 0; + + if (wdog_dd->do_ipi_ping) + ping_other_cpus(wdog_dd); + + do { + ret = wait_event_interruptible(wdog_dd->pet_complete, + wdog_dd->user_pet_complete); + } while (ret != 0); + + wdog_dd->timer_expired = false; + wdog_dd->user_pet_complete = !wdog_dd->user_pet_enabled; + + if (enable) { + delay_time = msecs_to_jiffies(wdog_dd->pet_time); + pet_watchdog(wdog_dd); + } + /* Check again before scheduling + * Could have been changed on other cpu + */ + if(!kthread_should_stop()) + mod_timer(&wdog_dd->pet_timer, jiffies + delay_time); + } + return 0; +} + +static int wdog_cpu_pm_notify(struct notifier_block *self, + unsigned long action, void *v) +{ + int cpu; + + cpu = raw_smp_processor_id(); + + switch (action) { + case CPU_PM_ENTER: + wdog_data->cpu_idle_pc_state[cpu] = 1; + break; + case CPU_PM_ENTER_FAILED: + case CPU_PM_EXIT: + wdog_data->cpu_idle_pc_state[cpu] = 0; + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block wdog_cpu_pm_nb = { + .notifier_call = wdog_cpu_pm_notify, +}; + +static int msm_watchdog_remove(struct platform_device *pdev) +{ + struct msm_watchdog_data *wdog_dd = platform_get_drvdata(pdev); + + if (!IPI_CORES_IN_LPM) + cpu_pm_unregister_notifier(&wdog_cpu_pm_nb); + + mutex_lock(&wdog_dd->disable_lock); + if (enable) + wdog_disable(wdog_dd); + + mutex_unlock(&wdog_dd->disable_lock); + device_remove_file(wdog_dd->dev, &dev_attr_disable); + if (wdog_dd->irq_ppi) + free_percpu(wdog_dd->wdog_cpu_dd); + irq_dispose_mapping(wdog_dd->bark_irq); + dev_info(wdog_dd->dev, "MSM Watchdog Exit - Deactivated\n"); + del_timer_sync(&wdog_dd->pet_timer); + wdog_dd->timer_expired = true; + wdog_dd->user_pet_complete = true; + kthread_stop(wdog_dd->watchdog_task); + return 0; +} + +void msm_trigger_wdog_bite(void) +{ + if (!wdog_data) + return; + dev_err(wdog_data->dev, "Causing a watchdog bite!\n"); + __raw_writel(1, wdog_data->base + WDT0_BITE_TIME); + /* Mke sure bite time is written before we reset */ + mb(); + __raw_writel(1, wdog_data->base + WDT0_RST); + /* Make sure we wait only after reset */ + mb(); + /* Delay to make sure bite occurs */ + mdelay(10000); + dev_err(wdog_data->dev, "Wdog - STS: 0x%x, CTL: 0x%x, BARK TIME: 0x%x, BITE TIME: 0x%x\n", + __raw_readl(wdog_data->base + WDT0_STS), + __raw_readl(wdog_data->base + WDT0_EN), + __raw_readl(wdog_data->base + WDT0_BARK_TIME), + __raw_readl(wdog_data->base + WDT0_BITE_TIME)); +} +EXPORT_SYMBOL(msm_trigger_wdog_bite); + +static irqreturn_t wdog_bark_handler(int irq, void *dev_id) +{ + struct msm_watchdog_data *wdog_dd = dev_id; + unsigned long nanosec_rem; + unsigned long long t = sched_clock(); + + nanosec_rem = do_div(t, 1000000000); + dev_info(wdog_dd->dev, "Watchdog bark! Now = %lu.%06lu\n", + (unsigned long) t, nanosec_rem / 1000); + + nanosec_rem = do_div(wdog_dd->last_pet, 1000000000); + dev_info(wdog_dd->dev, "Watchdog last pet at %lu.%06lu\n", + (unsigned long) wdog_dd->last_pet, nanosec_rem / 1000); + if (wdog_dd->do_ipi_ping) + dump_cpu_alive_mask(wdog_dd); + msm_trigger_wdog_bite(); + panic("Failed to cause a watchdog bite! - Falling back to kernel panic!"); + return IRQ_HANDLED; +} + +static irqreturn_t wdog_ppi_bark(int irq, void *dev_id) +{ + struct msm_watchdog_data *wdog_dd = dev_id; + return wdog_bark_handler(irq, wdog_dd); +} + +static int init_watchdog_sysfs(struct msm_watchdog_data *wdog_dd) +{ + int error = 0; + + error |= device_create_file(wdog_dd->dev, &dev_attr_disable); + + if (of_property_read_bool(wdog_dd->dev->of_node, + "qcom,userspace-watchdog")) { + error |= device_create_file(wdog_dd->dev, &dev_attr_pet_time); + error |= device_create_file(wdog_dd->dev, + &dev_attr_user_pet_enabled); + } + + if (error) + dev_err(wdog_dd->dev, "cannot create sysfs attribute\n"); + + return error; +} + +static void init_watchdog_data(struct msm_watchdog_data *wdog_dd) +{ + unsigned long delay_time; + uint32_t val; + u64 timeout; + int ret; + + if (wdog_dd->irq_ppi) { + wdog_dd->wdog_cpu_dd = alloc_percpu(struct msm_watchdog_data *); + if (!wdog_dd->wdog_cpu_dd) { + dev_err(wdog_dd->dev, "fail to allocate cpu data\n"); + return; + } + *raw_cpu_ptr(wdog_dd->wdog_cpu_dd) = wdog_dd; + ret = request_percpu_irq(wdog_dd->bark_irq, wdog_ppi_bark, + "apps_wdog_bark", + wdog_dd->wdog_cpu_dd); + if (ret) { + dev_err(wdog_dd->dev, "failed to request bark irq\n"); + free_percpu(wdog_dd->wdog_cpu_dd); + return; + } + } else { + ret = devm_request_irq(wdog_dd->dev, wdog_dd->bark_irq, + wdog_bark_handler, IRQF_TRIGGER_RISING, + "apps_wdog_bark", wdog_dd); + if (ret) { + dev_err(wdog_dd->dev, "failed to request bark irq\n"); + return; + } + } + delay_time = msecs_to_jiffies(wdog_dd->pet_time); + wdog_dd->min_slack_ticks = UINT_MAX; + wdog_dd->min_slack_ns = ULLONG_MAX; + timeout = (wdog_dd->bark_time * WDT_HZ)/1000; + __raw_writel(timeout, wdog_dd->base + WDT0_BARK_TIME); + __raw_writel(timeout + 3*WDT_HZ, wdog_dd->base + WDT0_BITE_TIME); + + wdog_dd->panic_blk.notifier_call = panic_wdog_handler; + atomic_notifier_chain_register(&panic_notifier_list, + &wdog_dd->panic_blk); + mutex_init(&wdog_dd->disable_lock); + init_waitqueue_head(&wdog_dd->pet_complete); + wdog_dd->timer_expired = false; + wdog_dd->user_pet_complete = true; + wdog_dd->user_pet_enabled = false; + wake_up_process(wdog_dd->watchdog_task); + timer_setup(&wdog_dd->pet_timer, pet_task_wakeup, 0); + wdog_dd->pet_timer.expires = jiffies + delay_time; + add_timer(&wdog_dd->pet_timer); + + val = BIT(EN); + if (wdog_dd->wakeup_irq_enable) + val |= BIT(UNMASKED_INT_EN); + __raw_writel(val, wdog_dd->base + WDT0_EN); + __raw_writel(1, wdog_dd->base + WDT0_RST); + wdog_dd->last_pet = sched_clock(); + wdog_dd->enabled = true; + + init_watchdog_sysfs(wdog_dd); + + if (wdog_dd->irq_ppi) + enable_percpu_irq(wdog_dd->bark_irq, 0); + if (!IPI_CORES_IN_LPM) + cpu_pm_register_notifier(&wdog_cpu_pm_nb); + dev_info(wdog_dd->dev, "MSM Watchdog Initialized\n"); +} + +static const struct of_device_id msm_wdog_match_table[] = { + { .compatible = "qcom,msm-watchdog" }, + {} +}; + +static void dump_pdata(struct msm_watchdog_data *pdata) +{ + dev_dbg(pdata->dev, "wdog bark_time %d", pdata->bark_time); + dev_dbg(pdata->dev, "wdog pet_time %d", pdata->pet_time); + dev_dbg(pdata->dev, "wdog perform ipi ping %d", pdata->do_ipi_ping); + dev_dbg(pdata->dev, "wdog base address is 0x%lx\n", (unsigned long) + pdata->base); +} + +static int msm_wdog_dt_to_pdata(struct platform_device *pdev, + struct msm_watchdog_data *pdata) +{ + struct device_node *node = pdev->dev.of_node; + struct resource *res; + int ret; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "wdt-base"); + if (!res) + return -ENODEV; + + pdata->base = devm_ioremap_resource(&pdev->dev, res); + if (!pdata->base) { + dev_err(&pdev->dev, "%s cannot map wdog register space\n", + __func__); + return -ENXIO; + } + + pdata->bark_irq = platform_get_irq(pdev, 0); + pdata->bite_irq = platform_get_irq(pdev, 1); + ret = of_property_read_u32(node, "qcom,bark-time", &pdata->bark_time); + if (ret) { + dev_err(&pdev->dev, "reading bark time failed\n"); + return -ENXIO; + } + ret = of_property_read_u32(node, "qcom,pet-time", &pdata->pet_time); + if (ret) { + dev_err(&pdev->dev, "reading pet time failed\n"); + return -ENXIO; + } + pdata->do_ipi_ping = of_property_read_bool(node, "qcom,ipi-ping"); + if (!pdata->bark_time) { + dev_err(&pdev->dev, "%s watchdog bark time not setup\n", + __func__); + return -ENXIO; + } + if (!pdata->pet_time) { + dev_err(&pdev->dev, "%s watchdog pet time not setup\n", + __func__); + return -ENXIO; + } + pdata->wakeup_irq_enable = of_property_read_bool(node, + "qcom,wakeup-enable"); + pdata->irq_ppi = irq_is_percpu(pdata->bark_irq); + dump_pdata(pdata); + return 0; +} + +static int msm_watchdog_probe(struct platform_device *pdev) +{ + struct msm_watchdog_data *wdog_dd; + struct md_region md_entry; + int ret; + + if (!enable) + return -ENODEV; + wdog_dd = devm_kzalloc(&pdev->dev, sizeof(*wdog_dd), GFP_KERNEL); + if (!wdog_dd) + return -ENOMEM; + ret = msm_wdog_dt_to_pdata(pdev, wdog_dd); + if (ret) + goto err; + + wdog_data = wdog_dd; + wdog_dd->dev = &pdev->dev; + platform_set_drvdata(pdev, wdog_dd); + cpumask_clear(&wdog_dd->alive_mask); + wdog_dd->watchdog_task = kthread_create(watchdog_kthread, wdog_dd, + "msm_watchdog"); + if (IS_ERR(wdog_dd->watchdog_task)) { + ret = PTR_ERR(wdog_dd->watchdog_task); + goto err; + } + init_watchdog_data(wdog_dd); + + /* Add wdog info to minidump table */ + strlcpy(md_entry.name, "KWDOGDATA", sizeof(md_entry.name)); + md_entry.virt_addr = (uintptr_t)wdog_dd; + md_entry.phys_addr = virt_to_phys(wdog_dd); + md_entry.size = sizeof(*wdog_dd); + if (msm_minidump_add_region(&md_entry)) + dev_err(wdog_dd->dev, "Failed to add Wdt data in Minidump\n"); + return 0; +err: + return ret; +} + +#ifdef CONFIG_PM_SLEEP +static const struct dev_pm_ops msm_watchdog_dev_pm_ops = { + .suspend_noirq = msm_watchdog_suspend, + .resume_noirq = msm_watchdog_resume, +}; +#else +static const struct dev_pm_ops msm_watchdog_dev_pm_ops = { +}; +#endif + +static struct platform_driver msm_watchdog_driver = { + .probe = msm_watchdog_probe, + .remove = msm_watchdog_remove, + .driver = { + .name = "msm_watchdog", + .pm = &msm_watchdog_dev_pm_ops, + .of_match_table = msm_wdog_match_table, + }, +}; + +static int __init init_watchdog(void) +{ + return platform_driver_register(&msm_watchdog_driver); +} +#if IS_MODULE(CONFIG_QCOM_WATCHDOG) +module_init(init_watchdog); +#else +pure_initcall(init_watchdog); +#endif + +static __exit void exit_watchdog(void) +{ + platform_driver_unregister(&msm_watchdog_driver); +} +module_exit(exit_watchdog); + +MODULE_DESCRIPTION("MSM Watchdog Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/soc/qcom/watchdog.h b/include/soc/qcom/watchdog.h new file mode 100644 index 000000000000..cac2ea037a0d --- /dev/null +++ b/include/soc/qcom/watchdog.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2019, The Linux Foundation. All rights reserved. + */ + +#ifndef _SOC_QCOM_WATCHDOG_H_ +#define _SOC_QCOM_WATCHDOG_H_ + +#if IS_ENABLED(CONFIG_QCOM_WATCHDOG) +void msm_trigger_wdog_bite(void); +#else +static inline void msm_trigger_wdog_bite(void) { } +#endif + +#endif