firmware/br-ext-chip-ingenic/board/t40/kernel/patches/00000-drivers_watchdog_inge...

585 lines
15 KiB
Diff

diff -drupN a/drivers/watchdog/ingenic_wdt.c b/drivers/watchdog/ingenic_wdt.c
--- a/drivers/watchdog/ingenic_wdt.c 1970-01-01 03:00:00.000000000 +0300
+++ b/drivers/watchdog/ingenic_wdt.c 2022-06-09 05:02:34.000000000 +0300
@@ -0,0 +1,580 @@
+/*
+ * Copyright (C) 2017, bo.liu <bo.liu@ingenic.com>
+ * INGENIC Watchdog driver
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/watchdog.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/device.h>
+#include <linux/clk.h>
+#include <linux/reboot.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/ingenic-tcu.h>
+#include <soc/xburst/reboot.h>
+#include <jz_proc.h>
+#include <soc/base.h>
+
+#define DEFAULT_HEARTBEAT 5
+#define MAX_HEARTBEAT 2048
+
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout,
+ "Watchdog cannot be stopped once started (default="
+ __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+static unsigned int heartbeat = DEFAULT_HEARTBEAT;
+module_param(heartbeat, uint, 0);
+MODULE_PARM_DESC(heartbeat,
+ "Watchdog heartbeat period in seconds from 1 to "
+ __MODULE_STRING(MAX_HEARTBEAT) ", default "
+ __MODULE_STRING(DEFAULT_HEARTBEAT));
+
+struct ingenic_wdt_drvdata {
+ struct watchdog_device wdt;
+ struct platform_device *pdev;
+ const struct mfd_cell *cell;
+
+ struct clk *ext_clk;
+ struct notifier_block restart_handler;
+};
+
+static int ingenic_wdt_ping(struct watchdog_device *wdt_dev)
+{
+ ingenic_watchdog_set_count(0);
+ return 0;
+}
+
+static inline int get_clk_div(unsigned int *timeout_value)
+{
+ int clk_div = TCU_PRESCALE_1;
+
+ while (*timeout_value > 0xffff) {
+ if (clk_div == TCU_PRESCALE_1024) {
+ /* Requested timeout too high;
+ * use highest possible value. */
+ *timeout_value = 0xffff;
+ clk_div = -1;
+ break;
+ }
+ *timeout_value >>= 2;
+ clk_div += 1;
+ }
+
+ return clk_div;
+}
+static void wdt_config(struct ingenic_wdt_drvdata *drvdata,
+ unsigned int new_timeout)
+{
+ unsigned int rtc_clk_rate;
+ //unsigned int ext_clk_rate;
+ unsigned int timeout_value;
+ unsigned int clk_src;
+ int clk_div = 0;
+
+ rtc_clk_rate = 24000000 / 512;
+ clk_src = TCU_CLKSRC_RTC;
+ //ext_clk_rate = clk_get_rate(drvdata->ext_clk);
+ //timeout_value = ext_clk_rate * new_timeout;
+ timeout_value = rtc_clk_rate * new_timeout;
+ clk_div = get_clk_div(&timeout_value);
+ if(clk_div < 0){
+ printk("Requested timeout too high, use highest possible value\n");
+ clk_div = TCU_PRESCALE_1024;
+ timeout_value = 0xffff;
+ }
+
+ ingenic_watchdog_config((clk_div << 3) | clk_src, timeout_value);
+}
+
+static int ingenic_wdt_set_timeout(struct watchdog_device *wdt_dev,
+ unsigned int new_timeout)
+{
+ struct ingenic_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
+
+ wdt_config(drvdata, new_timeout);
+ wdt_dev->timeout = new_timeout;
+ return 0;
+}
+
+static int ingenic_wdt_start(struct watchdog_device *wdt_dev)
+{
+ struct ingenic_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
+
+ drvdata->cell->enable(drvdata->pdev);
+ ingenic_wdt_set_timeout(wdt_dev, wdt_dev->timeout);
+ return 0;
+}
+
+static int ingenic_wdt_stop(struct watchdog_device *wdt_dev)
+{
+ struct ingenic_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
+ drvdata->cell->disable(drvdata->pdev);
+ return 0;
+}
+
+static const struct watchdog_info ingenic_wdt_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .identity = "ingenic Watchdog",
+};
+
+static const struct watchdog_ops ingenic_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = ingenic_wdt_start,
+ .stop = ingenic_wdt_stop,
+ .ping = ingenic_wdt_ping,
+ .set_timeout = ingenic_wdt_set_timeout,
+};
+
+static int ingenic_reset_handler(struct notifier_block *this, unsigned long mode,
+ void *cmd)
+{
+ struct ingenic_wdt_drvdata *drvdata = container_of(this,
+ struct ingenic_wdt_drvdata, restart_handler);
+ struct watchdog_device *wdd = &drvdata->wdt;
+
+ if (cmd && !strcmp(cmd, REBOOT_CMD_RECOVERY))
+ ingenic_recovery_sign();
+ else
+ ingenic_reboot_sign();
+
+ if (cmd && !strcmp(cmd, REBOOT_CMD_SOFTBURN))
+ ingenic_softburn_sign();
+
+ mutex_lock(&wdd->lock);
+
+ drvdata->cell->enable(drvdata->pdev);
+
+ ingenic_watchdog_config((3 << 3)|TCU_CLKSRC_RTC, 4);
+ while (1) {
+ mdelay(500);
+ pr_err("wdt reset failed, Never be here\n");
+ }
+ mutex_unlock(&wdd->lock);
+ return NOTIFY_DONE;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id ingenic_wdt_of_matches[] = {
+ { .compatible = "ingenic,watchdog", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ingenic_wdt_of_matches)
+#endif
+
+static ssize_t watchdog_cmd_set(struct file *file, const char __user *buffer, size_t count, loff_t *f_pos);
+static int watchdog_cmd_open(struct inode *inode, struct file *file);
+static const struct file_operations watchdog_cmd_fops ={
+ .read = seq_read,
+ .open = watchdog_cmd_open,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .write = watchdog_cmd_set,
+};
+
+
+static struct watchdog_device *m_wdt;
+static int ingenic_wdt_probe(struct platform_device *pdev)
+{
+ struct ingenic_wdt_drvdata *drvdata;
+ struct watchdog_device *ingenic_wdt;
+ int ret;
+ struct proc_dir_entry *proc;
+
+ drvdata = devm_kzalloc(&pdev->dev, sizeof(struct ingenic_wdt_drvdata),
+ GFP_KERNEL);
+ if (!drvdata) {
+ dev_err(&pdev->dev, "Unable to alloacate watchdog device\n");
+ return -ENOMEM;
+ }
+
+ drvdata->cell = mfd_get_cell(pdev);
+ if (!drvdata->cell) {
+ dev_err(&pdev->dev, "Failed to get mfd cell\n");
+ return -ENOMEM;
+ }
+ drvdata->pdev = pdev;
+ if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT)
+ heartbeat = DEFAULT_HEARTBEAT;
+
+ m_wdt = ingenic_wdt = &drvdata->wdt;
+ ingenic_wdt->info = &ingenic_wdt_info;
+ ingenic_wdt->ops = &ingenic_wdt_ops;
+ ingenic_wdt->timeout = heartbeat;
+ ingenic_wdt->min_timeout = 1;
+ ingenic_wdt->max_timeout = MAX_HEARTBEAT;
+ ingenic_wdt->parent = &pdev->dev;
+ watchdog_set_nowayout(ingenic_wdt, nowayout);
+ watchdog_set_drvdata(ingenic_wdt, drvdata);
+
+ drvdata->ext_clk = devm_clk_get(&pdev->dev, "ext");
+ if (IS_ERR(drvdata->ext_clk)) {
+ dev_err(&pdev->dev, "cannot find EXT clock\n");
+ ret = PTR_ERR(drvdata->ext_clk);
+ goto err_out;
+ }
+
+ ret = watchdog_register_device(&drvdata->wdt);
+ if (ret < 0)
+ goto err_disable_ext_clk;
+
+ platform_set_drvdata(pdev, drvdata);
+
+ drvdata->restart_handler.notifier_call = ingenic_reset_handler;
+ drvdata->restart_handler.priority = WDT_RESET_PROR;
+ ret = register_restart_handler(&drvdata->restart_handler);
+ if (ret)
+ dev_warn(&pdev->dev,
+ "cannot register restart handler (err=%d)\n", ret);
+
+ /* proc info */
+ proc = jz_proc_mkdir("watchdog");
+ if (!proc) {
+ printk("create mdio info failed!\n");
+ }
+ proc_create_data("reset", S_IRUGO, proc, &watchdog_cmd_fops, NULL);
+
+ return 0;
+
+err_disable_ext_clk:
+ clk_put(drvdata->ext_clk);
+err_out:
+ return ret;
+}
+
+static int ingenic_wdt_remove(struct platform_device *pdev)
+{
+ struct ingenic_wdt_drvdata *drvdata = platform_get_drvdata(pdev);
+
+ ingenic_wdt_stop(&drvdata->wdt);
+ watchdog_unregister_device(&drvdata->wdt);
+ clk_put(drvdata->ext_clk);
+ devm_kfree(&pdev->dev, drvdata);
+ return 0;
+}
+
+static struct platform_driver ingenic_wdt_driver = {
+ .probe = ingenic_wdt_probe,
+ .remove = ingenic_wdt_remove,
+ .driver = {
+ .name = "ingenic,watchdog",
+ .of_match_table = of_match_ptr(ingenic_wdt_of_matches),
+ },
+};
+
+module_platform_driver(ingenic_wdt_driver);
+
+/**********************ingenic utils(deprecated)*****************************************/
+struct wdt_utils {
+ struct watchdog_device *wdt;
+ unsigned msecs;
+
+ unsigned stop;
+ struct task_struct *task;
+ unsigned short count;
+};
+
+static __deprecated int reset_task(void *data) {
+ struct wdt_utils *wdt_utils = data;
+ const struct sched_param param = {
+ .sched_priority = MAX_RT_PRIO-1,
+ };
+ sched_setscheduler(current,SCHED_RR,&param);
+
+ if (test_and_set_bit(WDOG_DEV_OPEN, &wdt_utils->wdt->status)) {
+ pr_warn("watchdog conflict\n");
+ wdt_utils->stop = 1;
+ return 0;
+ }
+
+ mutex_lock(&wdt_utils->wdt->lock);
+
+ ingenic_wdt_set_timeout(wdt_utils->wdt, (wdt_utils->msecs + 1000 - 1)/1000);
+ ingenic_wdt_start(wdt_utils->wdt);
+
+ mutex_unlock(&wdt_utils->wdt->lock);
+
+ while (1) {
+ if(kthread_should_stop()) {
+ clear_bit(WDOG_DEV_OPEN, &wdt_utils->wdt->status);
+ mutex_lock(&wdt_utils->wdt->lock);
+
+ ingenic_wdt_stop(wdt_utils->wdt);
+
+ mutex_unlock(&wdt_utils->wdt->lock);
+ break;
+ }
+
+ mutex_lock(&wdt_utils->wdt->lock);
+
+ ingenic_wdt_ping(wdt_utils->wdt);
+
+ mutex_unlock(&wdt_utils->wdt->lock);
+ msleep(wdt_utils->wdt->timeout / 3);
+ }
+
+ return 0;
+}
+
+static __deprecated ssize_t wdt_time_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct wdt_utils *wdt = dev_get_drvdata(dev);
+ return sprintf(buf, "%d msecs\n", wdt->msecs);
+}
+static __deprecated ssize_t wdt_time_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct wdt_utils *wdt = dev_get_drvdata(dev);
+ unsigned int msecs;
+
+ if(!wdt->stop)
+ return -EBUSY;
+
+ sscanf(buf,"%d\n",&msecs);
+
+ if(msecs < 1000)
+ msecs = 1000;
+ if(msecs > 30000)
+ msecs = 30000;
+
+ wdt->msecs = msecs;
+ return count;
+}
+static __deprecated DEVICE_ATTR_RW(wdt_time);
+
+static __deprecated ssize_t wdt_control_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct wdt_utils *wdt = dev_get_drvdata(dev);
+ return sprintf(buf, "%s\n", wdt->stop?">off<on\n":"off>on<\n");
+}
+
+static __deprecated ssize_t wdt_control_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct wdt_utils *wdt = dev_get_drvdata(dev);
+
+ if(!strncmp(buf,"on",2) && (wdt->stop == 1)) {
+ wdt->task = kthread_run(reset_task, wdt, "reset_task%d",wdt->count++);
+ wdt->stop = 0;
+ } else if(!strncmp(buf,"off",3) && (wdt->stop == 0)) {
+ kthread_stop(wdt->task);
+ wdt->stop = 1;
+ }
+ return count;
+}
+static __deprecated DEVICE_ATTR_RW(wdt_control);
+
+static __deprecated char *reset_command[] = {"wdt", "hibernate","recovery", "burnerboot"};
+static __deprecated ssize_t reset_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ int len = 0, i;
+
+ for(i = 0; i < ARRAY_SIZE(reset_command); i++)
+ len += sprintf(buf + len, "%s\t", reset_command[i]);
+ len += sprintf(buf + len, "\n");
+
+ return len;
+}
+
+static __deprecated ssize_t reset_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int command_size = 0;
+ int i;
+ char *cmd = NULL;
+
+ if(count == 0)
+ return -EINVAL;
+
+ command_size = ARRAY_SIZE(reset_command);
+ for(i = 0;i < command_size; i++) {
+ if(!strncmp(buf, reset_command[i], strlen(reset_command[i])))
+ break;
+ }
+
+ switch(i) {
+ case 1:
+ reboot_mode = REBOOT_HARD;
+ break;
+ case 2:
+ cmd = "recovery";
+ break;
+ case 3:
+ cmd = "softburn";
+ break;
+ default:
+ case 0:
+ break;
+ }
+ kernel_restart(cmd);
+ return count;
+}
+static __deprecated DEVICE_ATTR_RW(reset);
+
+static __deprecated struct attribute *reset_attrs[] = {
+ &dev_attr_wdt_time.attr,
+ &dev_attr_wdt_control.attr,
+ &dev_attr_reset.attr,
+ NULL
+};
+
+static __deprecated const struct attribute_group attr_group = {
+ .attrs = reset_attrs,
+};
+
+static __deprecated int wdt_utils_probe(struct platform_device *pdev)
+{
+ struct wdt_utils *wdt;
+ int ret;
+
+ wdt = devm_kzalloc(&pdev->dev, sizeof(struct wdt_utils),GFP_KERNEL);
+ if(!wdt)
+ return -ENOMEM;
+ wdt->wdt = m_wdt;
+ wdt->count = 0;
+ wdt->stop = 1;
+ wdt->msecs = heartbeat * 1000;
+ dev_set_drvdata(&pdev->dev, wdt);
+ ret = sysfs_create_group(&pdev->dev.kobj, &attr_group);
+ if (ret)
+ printk("watchdog utils regist failed\n");
+ return ret;
+}
+
+static __deprecated void wdt_utils_shutdown(struct platform_device *pdev)
+{
+ struct wdt_utils *wdt_utils = dev_get_drvdata(&pdev->dev);
+ ingenic_wdt_stop(wdt_utils->wdt);
+}
+
+static __deprecated int wdt_utils_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct wdt_utils *wdt = dev_get_drvdata(&pdev->dev);
+ if (wdt->stop)
+ return 0;
+ kthread_stop(wdt->task);
+ return 0;
+}
+
+
+static __deprecated int wdt_utils_resume(struct platform_device *pdev)
+{
+ struct wdt_utils *wdt = dev_get_drvdata(&pdev->dev);
+ if(wdt->stop)
+ return 0;
+ wdt->task = kthread_run(reset_task, wdt, "reset_task%d",wdt->count++);
+ return 0;
+}
+
+static __deprecated struct platform_device wdt_utils_pdev = {
+ .name = "wdt_utils",
+};
+
+static __deprecated struct platform_driver wdt_utils_pdrv = {
+ .probe = wdt_utils_probe,
+ .shutdown = wdt_utils_shutdown,
+ .suspend = wdt_utils_suspend,
+ .resume = wdt_utils_resume,
+ .driver = {
+ .name = "wdt_utils",
+ .owner = THIS_MODULE,
+ },
+};
+
+static __deprecated int __init init_reset(void)
+{
+ platform_driver_register(&wdt_utils_pdrv);
+ platform_device_register(&wdt_utils_pdev);
+ return 0;
+}
+device_initcall_sync(init_reset);
+MODULE_AUTHOR("bo.liu <bo.liu@ingenic.com>");
+MODULE_DESCRIPTION("ingenic Watchdog Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ingenic-wdt");
+
+
+/* cmd */
+#define WATCHDOG_CMD_BUF_SIZE 100
+static uint8_t watchdog_cmd_buf[100];
+static int watchdog_cmd_show(struct seq_file *m, void *v)
+{
+ int len = 0;
+ len += seq_printf(m ,"%s\n", watchdog_cmd_buf);
+ return len;
+}
+
+static ssize_t watchdog_cmd_set(struct file *file, const char __user *buffer, size_t count, loff_t *f_pos)
+{
+ int cmd_value = 0;
+ unsigned int reg04 = 0,reglc = 0;
+
+ char *buf = kzalloc((count+1), GFP_KERNEL);
+ if(!buf)
+ return -ENOMEM;
+ if(copy_from_user(buf, buffer, count))
+ {
+ kfree(buf);
+ return EFAULT;
+ }
+ cmd_value = simple_strtoull(buf, NULL, 0);
+
+ mdelay(1000);
+ if(1 == cmd_value) {
+ reg04 = inl(TCU_IOBASE + 0x04);
+ if(reg04 & 0x1) {
+ outl(0 << 0, TCU_IOBASE + 0x4);
+ }
+
+ outl(0x1a, TCU_IOBASE + 0xc);
+
+ reglc = inl(TCU_IOBASE + 0x1c);
+ if(reglc & 0x10000) {
+ outl(1 << 16, TCU_IOBASE + 0x3c);
+ outl(1 << 16, TCU_IOBASE + 0x2c);
+ }
+ outl(0x0000, TCU_IOBASE + 0x0);
+ outl(0x0000, TCU_IOBASE + 0x8);
+
+ reg04 = inl(TCU_IOBASE + 0x04);
+ if(!(reg04 & 0x1)) {
+ outl(1 << 0, TCU_IOBASE + 0x4);
+ }
+ printk("watchdog reboot system!!!\n");
+ }
+
+ kfree(buf);
+ return count;
+}
+static int watchdog_cmd_open(struct inode *inode, struct file *file)
+{
+ return single_open_size(file, watchdog_cmd_show, PDE_DATA(inode),8192);
+}