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