--- zzzz-none-000/linux-4.4.60/drivers/watchdog/qcom-wdt.c 2017-04-08 07:53:53.000000000 +0000 +++ wasp-540e-714/linux-4.4.60/drivers/watchdog/qcom-wdt.c 2019-07-03 09:21:34.000000000 +0000 @@ -1,4 +1,4 @@ -/* Copyright (c) 2014, The Linux Foundation. All rights reserved. +/* Copyright (c) 2014,2016 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 @@ -16,36 +16,237 @@ #include #include #include +#include #include #include #include +#include +#include +#include +#include +#include +#include + +static int in_panic; + +enum wdt_reg { + WDT_RST, + WDT_EN, + WDT_BARK_TIME, + WDT_BITE_TIME, +}; + +static const u32 reg_offset_data_apcs_tmr[] = { + [WDT_RST] = 0x38, + [WDT_EN] = 0x40, + [WDT_BARK_TIME] = 0x4C, + [WDT_BITE_TIME] = 0x5C, +}; -#define WDT_RST 0x38 -#define WDT_EN 0x40 -#define WDT_BITE_TIME 0x5C +static const u32 reg_offset_data_kpss[] = { + [WDT_RST] = 0x4, + [WDT_EN] = 0x8, + [WDT_BARK_TIME] = 0x10, + [WDT_BITE_TIME] = 0x14, +}; + +struct qcom_wdt_props { + const u32 *layout; + unsigned int tlv_msg_offset; + unsigned int crashdump_page_size; +}; struct qcom_wdt { struct watchdog_device wdd; struct clk *clk; unsigned long rate; + unsigned int bite; struct notifier_block restart_nb; void __iomem *base; + const struct qcom_wdt_props *dev_props; + struct resource *tlv_res; +}; + +struct qcom_wdt_scm_tlv_msg { + unsigned char *msg_buffer; + unsigned char *cur_msg_buffer_pos; + unsigned int len; +}; + +#define QCOM_WDT_SCM_TLV_TYPE_SIZE 1 +#define QCOM_WDT_SCM_TLV_LEN_SIZE 2 +#define QCOM_WDT_SCM_TLV_TYPE_LEN_SIZE (QCOM_WDT_SCM_TLV_TYPE_SIZE +\ + QCOM_WDT_SCM_TLV_LEN_SIZE) +enum { + QCOM_WDT_LOG_DUMP_TYPE_INVALID, + QCOM_WDT_LOG_DUMP_TYPE_UNAME, }; +static void __iomem *wdt_addr(struct qcom_wdt *wdt, enum wdt_reg reg) +{ + return wdt->base + wdt->dev_props->layout[reg]; +}; + +static int qcom_wdt_scm_add_tlv(struct qcom_wdt_scm_tlv_msg *scm_tlv_msg, + unsigned char type, unsigned int size, const char *data) +{ + unsigned char *x = scm_tlv_msg->cur_msg_buffer_pos; + unsigned char *y = scm_tlv_msg->msg_buffer + scm_tlv_msg->len; + + if ((x + QCOM_WDT_SCM_TLV_TYPE_LEN_SIZE + size) >= y) + return -ENOBUFS; + + x[0] = type; + x[1] = size; + x[2] = size >> 8; + + memcpy(x + 3, data, size); + + scm_tlv_msg->cur_msg_buffer_pos += + (size + QCOM_WDT_SCM_TLV_TYPE_LEN_SIZE); + + return 0; +} + +static int qcom_wdt_scm_fill_log_dump_tlv( + struct qcom_wdt_scm_tlv_msg *scm_tlv_msg) +{ + struct new_utsname *uname; + int ret_val; + + uname = utsname(); + + ret_val = qcom_wdt_scm_add_tlv(scm_tlv_msg, + QCOM_WDT_LOG_DUMP_TYPE_UNAME, + sizeof(*uname), + (unsigned char *)uname); + + if (ret_val) + return ret_val; + + if (scm_tlv_msg->cur_msg_buffer_pos >= + scm_tlv_msg->msg_buffer + scm_tlv_msg->len) + return -ENOBUFS; + + *scm_tlv_msg->cur_msg_buffer_pos++ = QCOM_WDT_LOG_DUMP_TYPE_INVALID; + + return 0; +} + static inline struct qcom_wdt *to_qcom_wdt(struct watchdog_device *wdd) { return container_of(wdd, struct qcom_wdt, wdd); } -static int qcom_wdt_start(struct watchdog_device *wdd) +static int panic_prep_restart(struct notifier_block *this, + unsigned long event, void *ptr) +{ + in_panic = 1; + return NOTIFY_DONE; +} + +static struct notifier_block panic_blk = { + .notifier_call = panic_prep_restart, +}; + +static long qcom_wdt_configure_bark_dump(void *arg) +{ + void *scm_regsave; + struct qcom_wdt_scm_tlv_msg tlv_msg; + void *tlv_ptr; + resource_size_t tlv_base; + resource_size_t tlv_size; + struct qcom_wdt *wdt = (struct qcom_wdt *) arg; + const struct qcom_wdt_props *device_props = wdt->dev_props; + long ret = -ENOMEM; + struct resource *res = wdt->tlv_res; + + scm_regsave = (void *) __get_free_pages(GFP_KERNEL, + get_order(device_props->crashdump_page_size)); + if (!scm_regsave) + return -ENOMEM; + + ret = qcom_scm_regsave(SCM_SVC_UTIL, SCM_CMD_SET_REGSAVE, + scm_regsave, device_props->crashdump_page_size); + if (ret) { + pr_err("Setting register save address failed.\n" + "Registers won't be dumped on a dog bite\n"); + return ret; + } + + /* Initialize the tlv and fill all the details */ + tlv_msg.msg_buffer = scm_regsave + device_props->tlv_msg_offset; + tlv_msg.cur_msg_buffer_pos = tlv_msg.msg_buffer; + tlv_msg.len = device_props->crashdump_page_size - + device_props->tlv_msg_offset; + + ret = qcom_wdt_scm_fill_log_dump_tlv(&tlv_msg); + + /* if failed, we still return 0 because it should not + * affect the boot flow. The return value 0 does not + * necessarily indicate success in this function. + */ + if (ret) { + pr_err("log dump initialization failed\n"); + return 0; + } + + if (res) { + tlv_base = res->start; + tlv_size = resource_size(res); + res = request_mem_region(tlv_base, tlv_size, "tlv_dump"); + + if (!res) { + pr_err("requesting memory region failed\n"); + return 0; + } + + tlv_ptr = ioremap(tlv_base, tlv_size); + + if (!tlv_ptr) { + pr_err("mapping physical mem failed\n"); + release_mem_region(tlv_base, tlv_size); + return 0; + } + + memcpy_toio(tlv_ptr, tlv_msg.msg_buffer, tlv_msg.len); + iounmap(tlv_ptr); + release_mem_region(tlv_base, tlv_size); + } + + return 0; +} + +static int qcom_wdt_start_secure(struct watchdog_device *wdd) +{ + struct qcom_wdt *wdt = to_qcom_wdt(wdd); + + writel(0, wdt_addr(wdt, WDT_EN)); + writel(1, wdt_addr(wdt, WDT_RST)); + + if (wdt->bite) { + writel((wdd->timeout - 1) * wdt->rate, + wdt_addr(wdt, WDT_BARK_TIME)); + writel(wdd->timeout * wdt->rate, wdt_addr(wdt, WDT_BITE_TIME)); + } else { + writel(wdd->timeout * wdt->rate, wdt_addr(wdt, WDT_BARK_TIME)); + writel((wdd->timeout * wdt->rate) * 2, wdt_addr(wdt, WDT_BITE_TIME)); + } + + writel(1, wdt_addr(wdt, WDT_EN)); + return 0; +} + +static int qcom_wdt_start_nonsecure(struct watchdog_device *wdd) { struct qcom_wdt *wdt = to_qcom_wdt(wdd); - writel(0, wdt->base + WDT_EN); - writel(1, wdt->base + WDT_RST); - writel(wdd->timeout * wdt->rate, wdt->base + WDT_BITE_TIME); - writel(1, wdt->base + WDT_EN); + writel(0, wdt_addr(wdt, WDT_EN)); + writel(1, wdt_addr(wdt, WDT_RST)); + writel(wdd->timeout * wdt->rate, wdt_addr(wdt, WDT_BARK_TIME)); + writel(0x0FFFFFFF, wdt_addr(wdt, WDT_BITE_TIME)); + writel(1, wdt_addr(wdt, WDT_EN)); return 0; } @@ -53,7 +254,7 @@ { struct qcom_wdt *wdt = to_qcom_wdt(wdd); - writel(0, wdt->base + WDT_EN); + writel(0, wdt_addr(wdt, WDT_EN)); return 0; } @@ -61,7 +262,7 @@ { struct qcom_wdt *wdt = to_qcom_wdt(wdd); - writel(1, wdt->base + WDT_RST); + writel(1, wdt_addr(wdt, WDT_RST)); return 0; } @@ -69,11 +270,19 @@ unsigned int timeout) { wdd->timeout = timeout; - return qcom_wdt_start(wdd); + return wdd->ops->start(wdd); } -static const struct watchdog_ops qcom_wdt_ops = { - .start = qcom_wdt_start, +static const struct watchdog_ops qcom_wdt_ops_secure = { + .start = qcom_wdt_start_secure, + .stop = qcom_wdt_stop, + .ping = qcom_wdt_ping, + .set_timeout = qcom_wdt_set_timeout, + .owner = THIS_MODULE, +}; + +static const struct watchdog_ops qcom_wdt_ops_nonsecure = { + .start = qcom_wdt_start_nonsecure, .stop = qcom_wdt_stop, .ping = qcom_wdt_ping, .set_timeout = qcom_wdt_set_timeout, @@ -87,6 +296,47 @@ .identity = KBUILD_MODNAME, }; +const struct qcom_wdt_props qcom_wdt_props_ipq8064 = { + .layout = reg_offset_data_apcs_tmr, + .tlv_msg_offset = SZ_2K, + .crashdump_page_size = SZ_4K, +}; + +const struct qcom_wdt_props qcom_wdt_props_ipq807x = { + .layout = reg_offset_data_kpss, + .tlv_msg_offset = SZ_4K, + /* As SBL overwrites the NSS IMEM, TZ has to copy it to some memory + * on crash before it restarts the system. Hence, reserving of 384K + * is required to copy the NSS IMEM before restart is done. + * So that TZ can dump NSS dump data after the first 8K. + * Additionally 8K memory is allocated which can be used by TZ + * to dump PMIC memory. + * get_order function returns the next higher order as output, + * so when we pass 400K as argument 512K will be allocated. + * 112K is unused currently and can be used based on future needs. + */ + .crashdump_page_size = (SZ_8K + (384 * SZ_1K) + (SZ_8K)), +}; + +const struct qcom_wdt_props qcom_wdt_props_ipq40xx = { + .layout = reg_offset_data_kpss, + .tlv_msg_offset = SZ_2K, + .crashdump_page_size = SZ_4K, +}; + +static const struct of_device_id qcom_wdt_of_table[] = { + { .compatible = "qcom,kpss-wdt-ipq8064", + .data = (void *) &qcom_wdt_props_ipq8064, + }, + { .compatible = "qcom,kpss-wdt-ipq807x", + .data = (void *) &qcom_wdt_props_ipq807x, + }, + { .compatible = "qcom,kpss-wdt-ipq40xx", + .data = (void *) &qcom_wdt_props_ipq40xx, + }, + {} +}; +MODULE_DEVICE_TABLE(of, qcom_wdt_of_table); static int qcom_wdt_restart(struct notifier_block *nb, unsigned long action, void *data) { @@ -99,33 +349,84 @@ */ timeout = 128 * wdt->rate / 1000; - writel(0, wdt->base + WDT_EN); - writel(1, wdt->base + WDT_RST); - writel(timeout, wdt->base + WDT_BITE_TIME); - writel(1, wdt->base + WDT_EN); + writel(0, wdt_addr(wdt, WDT_EN)); + writel(1, wdt_addr(wdt, WDT_RST)); + if (in_panic) { + writel(timeout, wdt_addr(wdt, WDT_BARK_TIME)); + writel(2 * timeout, wdt_addr(wdt, WDT_BITE_TIME)); + } else { + writel(5 * timeout, wdt_addr(wdt, WDT_BARK_TIME)); + writel(timeout, wdt_addr(wdt, WDT_BITE_TIME)); + } + writel(1, wdt_addr(wdt, WDT_EN)); /* * Actually make sure the above sequence hits hardware before sleeping. */ wmb(); - msleep(150); + mdelay(150); return NOTIFY_DONE; } +static irqreturn_t wdt_bark_isr(int irq, void *wdd) +{ + struct qcom_wdt *wdt = to_qcom_wdt(wdd); + unsigned long nanosec_rem; + unsigned long long t = sched_clock(); + + printk(KERN_ERR, "Watch Dog Bark\n"); + + nanosec_rem = do_div(t, 1000000000); + pr_info("Watchdog bark! Now = %lu.%06lu\n", (unsigned long) t, + nanosec_rem / 1000); + pr_info("Causing a watchdog bite!"); + writel(0, wdt_addr(wdt, WDT_EN)); + writel(1, wdt_addr(wdt, WDT_BITE_TIME)); + mb(); /* Avoid unpredictable behaviour in concurrent executions */ + pr_info("Configuring Watchdog Timer\n"); + writel(1, wdt_addr(wdt, WDT_RST)); + writel(1, wdt_addr(wdt, WDT_EN)); + mb(); /* Make sure the above sequence hits hardware before Reboot. */ + pr_info("Waiting for Reboot\n"); + + mdelay(1); + pr_err("Wdog - CTL: 0x%x, BARK TIME: 0x%x, BITE TIME: 0x%x", + readl(wdt_addr(wdt, WDT_EN)), + readl(wdt_addr(wdt, WDT_BARK_TIME)), + readl(wdt_addr(wdt, WDT_BITE_TIME))); + return IRQ_HANDLED; +} + +void register_wdt_bark_irq(int irq, struct qcom_wdt *wdt) +{ + int ret; + + ret = request_irq(irq, wdt_bark_isr, IRQF_TRIGGER_HIGH, + "watchdog bark", wdt); + if (ret) + pr_err("error request_irq(irq_num:%d ) ret:%d\n", irq, ret); +} + static int qcom_wdt_probe(struct platform_device *pdev) { + const struct of_device_id *id; struct qcom_wdt *wdt; struct resource *res; struct device_node *np = pdev->dev.of_node; u32 percpu_offset; - int ret; + int ret, irq; + uint32_t val; wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); if (!wdt) return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq_byname(pdev, "bark_irq"); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "kpss_wdt"); + if (!res) { + dev_err(&pdev->dev, "%s: no mem resource\n", __func__); + return -EINVAL; + } /* We use CPU0's DGT for the watchdog */ if (of_property_read_u32(np, "cpu-offset", &percpu_offset)) @@ -138,6 +439,21 @@ if (IS_ERR(wdt->base)) return PTR_ERR(wdt->base); + wdt->tlv_res = platform_get_resource_byname + (pdev, IORESOURCE_MEM, "tlv"); + + id = of_match_device(qcom_wdt_of_table, &pdev->dev); + if (!id) + return -ENODEV; + + wdt->dev_props = (struct qcom_wdt_props *)id->data; + + if (wdt->dev_props->layout) + wdt->bite = 1; + + if (irq > 0) + register_wdt_bark_irq(irq, wdt); + wdt->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(wdt->clk)) { dev_err(&pdev->dev, "failed to get input clock\n"); @@ -166,11 +482,19 @@ goto err_clk_unprepare; } + ret = work_on_cpu(0, qcom_wdt_configure_bark_dump, wdt); + if (ret) + wdt->wdd.ops = &qcom_wdt_ops_nonsecure; + else + wdt->wdd.ops = &qcom_wdt_ops_secure; + wdt->wdd.dev = &pdev->dev; wdt->wdd.info = &qcom_wdt_info; - wdt->wdd.ops = &qcom_wdt_ops; wdt->wdd.min_timeout = 1; - wdt->wdd.max_timeout = 0x10000000U / wdt->rate; + if (!of_property_read_u32(np, "wdt-max-timeout", &val)) + wdt->wdd.max_timeout = val; + else + wdt->wdd.max_timeout = 0x10000000U / wdt->rate; wdt->wdd.parent = &pdev->dev; /* @@ -191,6 +515,7 @@ * WDT restart notifier has priority 0 (use as a last resort) */ wdt->restart_nb.notifier_call = qcom_wdt_restart; + atomic_notifier_chain_register(&panic_notifier_list, &panic_blk); ret = register_restart_handler(&wdt->restart_nb); if (ret) dev_err(&pdev->dev, "failed to setup restart handler\n"); @@ -213,13 +538,6 @@ return 0; } -static const struct of_device_id qcom_wdt_of_table[] = { - { .compatible = "qcom,kpss-timer" }, - { .compatible = "qcom,scss-timer" }, - { }, -}; -MODULE_DEVICE_TABLE(of, qcom_wdt_of_table); - static struct platform_driver qcom_watchdog_driver = { .probe = qcom_wdt_probe, .remove = qcom_wdt_remove,