1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (c) 2016 Yang Ling <gnaygnil@gmail.com>
4  */
5 
6 #include <linux/clk.h>
7 #include <linux/module.h>
8 #include <linux/platform_device.h>
9 #include <linux/watchdog.h>
10 
11 /* Loongson 1 Watchdog Register Definitions */
12 #define WDT_EN			0x0
13 #define WDT_TIMER		0x4
14 #define WDT_SET			0x8
15 
16 #define DEFAULT_HEARTBEAT	30
17 
18 static bool nowayout = WATCHDOG_NOWAYOUT;
19 module_param(nowayout, bool, 0444);
20 
21 static unsigned int heartbeat;
22 module_param(heartbeat, uint, 0444);
23 
24 struct ls1x_wdt_drvdata {
25 	void __iomem *base;
26 	struct clk *clk;
27 	unsigned long clk_rate;
28 	struct watchdog_device wdt;
29 };
30 
31 static int ls1x_wdt_ping(struct watchdog_device *wdt_dev)
32 {
33 	struct ls1x_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
34 
35 	writel(0x1, drvdata->base + WDT_SET);
36 
37 	return 0;
38 }
39 
40 static int ls1x_wdt_set_timeout(struct watchdog_device *wdt_dev,
41 				unsigned int timeout)
42 {
43 	struct ls1x_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
44 	unsigned int max_hw_heartbeat = wdt_dev->max_hw_heartbeat_ms / 1000;
45 	unsigned int counts;
46 
47 	wdt_dev->timeout = timeout;
48 
49 	counts = drvdata->clk_rate * min(timeout, max_hw_heartbeat);
50 	writel(counts, drvdata->base + WDT_TIMER);
51 
52 	return 0;
53 }
54 
55 static int ls1x_wdt_start(struct watchdog_device *wdt_dev)
56 {
57 	struct ls1x_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
58 
59 	writel(0x1, drvdata->base + WDT_EN);
60 
61 	return 0;
62 }
63 
64 static int ls1x_wdt_stop(struct watchdog_device *wdt_dev)
65 {
66 	struct ls1x_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
67 
68 	writel(0x0, drvdata->base + WDT_EN);
69 
70 	return 0;
71 }
72 
73 static int ls1x_wdt_restart(struct watchdog_device *wdt_dev,
74 			    unsigned long action, void *data)
75 {
76 	struct ls1x_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev);
77 
78 	writel(0x1, drvdata->base + WDT_EN);
79 	writel(0x1, drvdata->base + WDT_TIMER);
80 	writel(0x1, drvdata->base + WDT_SET);
81 
82 	return 0;
83 }
84 
85 static const struct watchdog_info ls1x_wdt_info = {
86 	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
87 	.identity = "Loongson1 Watchdog",
88 };
89 
90 static const struct watchdog_ops ls1x_wdt_ops = {
91 	.owner = THIS_MODULE,
92 	.start = ls1x_wdt_start,
93 	.stop = ls1x_wdt_stop,
94 	.ping = ls1x_wdt_ping,
95 	.set_timeout = ls1x_wdt_set_timeout,
96 	.restart = ls1x_wdt_restart,
97 };
98 
99 static int ls1x_wdt_probe(struct platform_device *pdev)
100 {
101 	struct device *dev = &pdev->dev;
102 	struct ls1x_wdt_drvdata *drvdata;
103 	struct watchdog_device *ls1x_wdt;
104 	unsigned long clk_rate;
105 	int err;
106 
107 	drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
108 	if (!drvdata)
109 		return -ENOMEM;
110 
111 	drvdata->base = devm_platform_ioremap_resource(pdev, 0);
112 	if (IS_ERR(drvdata->base))
113 		return PTR_ERR(drvdata->base);
114 
115 	drvdata->clk = devm_clk_get_enabled(dev, pdev->name);
116 	if (IS_ERR(drvdata->clk))
117 		return PTR_ERR(drvdata->clk);
118 
119 	clk_rate = clk_get_rate(drvdata->clk);
120 	if (!clk_rate)
121 		return -EINVAL;
122 	drvdata->clk_rate = clk_rate;
123 
124 	ls1x_wdt = &drvdata->wdt;
125 	ls1x_wdt->info = &ls1x_wdt_info;
126 	ls1x_wdt->ops = &ls1x_wdt_ops;
127 	ls1x_wdt->timeout = DEFAULT_HEARTBEAT;
128 	ls1x_wdt->min_timeout = 1;
129 	ls1x_wdt->max_hw_heartbeat_ms = U32_MAX / clk_rate * 1000;
130 	ls1x_wdt->parent = dev;
131 
132 	watchdog_init_timeout(ls1x_wdt, heartbeat, dev);
133 	watchdog_set_nowayout(ls1x_wdt, nowayout);
134 	watchdog_set_drvdata(ls1x_wdt, drvdata);
135 
136 	err = devm_watchdog_register_device(dev, &drvdata->wdt);
137 	if (err)
138 		return err;
139 
140 	platform_set_drvdata(pdev, drvdata);
141 
142 	dev_info(dev, "Loongson1 Watchdog driver registered\n");
143 
144 	return 0;
145 }
146 
147 static struct platform_driver ls1x_wdt_driver = {
148 	.probe = ls1x_wdt_probe,
149 	.driver = {
150 		.name = "ls1x-wdt",
151 	},
152 };
153 
154 module_platform_driver(ls1x_wdt_driver);
155 
156 MODULE_AUTHOR("Yang Ling <gnaygnil@gmail.com>");
157 MODULE_DESCRIPTION("Loongson1 Watchdog Driver");
158 MODULE_LICENSE("GPL");
159