Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
1d8565ee YL |
2 | /* |
3 | * Copyright (c) 2016 Yang Ling <gnaygnil@gmail.com> | |
1d8565ee YL |
4 | */ |
5 | ||
6 | #include <linux/clk.h> | |
7 | #include <linux/module.h> | |
8 | #include <linux/platform_device.h> | |
9 | #include <linux/watchdog.h> | |
2a31bf20 KZ |
10 | |
11 | /* Loongson 1 Watchdog Register Definitions */ | |
12 | #define WDT_EN 0x0 | |
13 | #define WDT_TIMER 0x4 | |
14 | #define WDT_SET 0x8 | |
1d8565ee YL |
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 | ||
2a31bf20 KZ |
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 | ||
1d8565ee YL |
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, | |
2a31bf20 | 96 | .restart = ls1x_wdt_restart, |
1d8565ee YL |
97 | }; |
98 | ||
99 | static int ls1x_wdt_probe(struct platform_device *pdev) | |
100 | { | |
fd56d6c9 | 101 | struct device *dev = &pdev->dev; |
1d8565ee YL |
102 | struct ls1x_wdt_drvdata *drvdata; |
103 | struct watchdog_device *ls1x_wdt; | |
104 | unsigned long clk_rate; | |
1d8565ee YL |
105 | int err; |
106 | ||
fd56d6c9 | 107 | drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL); |
1d8565ee YL |
108 | if (!drvdata) |
109 | return -ENOMEM; | |
110 | ||
0f0a6a28 | 111 | drvdata->base = devm_platform_ioremap_resource(pdev, 0); |
1d8565ee YL |
112 | if (IS_ERR(drvdata->base)) |
113 | return PTR_ERR(drvdata->base); | |
114 | ||
07d41160 | 115 | drvdata->clk = devm_clk_get_enabled(dev, pdev->name); |
1d8565ee YL |
116 | if (IS_ERR(drvdata->clk)) |
117 | return PTR_ERR(drvdata->clk); | |
118 | ||
1d8565ee | 119 | clk_rate = clk_get_rate(drvdata->clk); |
fd56d6c9 GR |
120 | if (!clk_rate) |
121 | return -EINVAL; | |
1d8565ee YL |
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; | |
fd56d6c9 | 130 | ls1x_wdt->parent = dev; |
1d8565ee | 131 | |
fd56d6c9 | 132 | watchdog_init_timeout(ls1x_wdt, heartbeat, dev); |
1d8565ee YL |
133 | watchdog_set_nowayout(ls1x_wdt, nowayout); |
134 | watchdog_set_drvdata(ls1x_wdt, drvdata); | |
135 | ||
fd56d6c9 | 136 | err = devm_watchdog_register_device(dev, &drvdata->wdt); |
7da54735 | 137 | if (err) |
fd56d6c9 | 138 | return err; |
1d8565ee YL |
139 | |
140 | platform_set_drvdata(pdev, drvdata); | |
141 | ||
fd56d6c9 | 142 | dev_info(dev, "Loongson1 Watchdog driver registered\n"); |
1d8565ee YL |
143 | |
144 | return 0; | |
145 | } | |
146 | ||
147 | static struct platform_driver ls1x_wdt_driver = { | |
148 | .probe = ls1x_wdt_probe, | |
1d8565ee YL |
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"); |