Commit | Line | Data |
---|---|---|
1d8565ee YL |
1 | /* |
2 | * Copyright (c) 2016 Yang Ling <gnaygnil@gmail.com> | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify it | |
5 | * under the terms of the GNU General Public License as published by the | |
6 | * Free Software Foundation; either version 2 of the License, or (at your | |
7 | * option) any later version. | |
8 | */ | |
9 | ||
10 | #include <linux/clk.h> | |
11 | #include <linux/module.h> | |
12 | #include <linux/platform_device.h> | |
13 | #include <linux/watchdog.h> | |
14 | #include <loongson1.h> | |
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 const struct watchdog_info ls1x_wdt_info = { | |
74 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, | |
75 | .identity = "Loongson1 Watchdog", | |
76 | }; | |
77 | ||
78 | static const struct watchdog_ops ls1x_wdt_ops = { | |
79 | .owner = THIS_MODULE, | |
80 | .start = ls1x_wdt_start, | |
81 | .stop = ls1x_wdt_stop, | |
82 | .ping = ls1x_wdt_ping, | |
83 | .set_timeout = ls1x_wdt_set_timeout, | |
84 | }; | |
85 | ||
86 | static int ls1x_wdt_probe(struct platform_device *pdev) | |
87 | { | |
88 | struct ls1x_wdt_drvdata *drvdata; | |
89 | struct watchdog_device *ls1x_wdt; | |
90 | unsigned long clk_rate; | |
91 | struct resource *res; | |
92 | int err; | |
93 | ||
94 | drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); | |
95 | if (!drvdata) | |
96 | return -ENOMEM; | |
97 | ||
98 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
99 | drvdata->base = devm_ioremap_resource(&pdev->dev, res); | |
100 | if (IS_ERR(drvdata->base)) | |
101 | return PTR_ERR(drvdata->base); | |
102 | ||
103 | drvdata->clk = devm_clk_get(&pdev->dev, pdev->name); | |
104 | if (IS_ERR(drvdata->clk)) | |
105 | return PTR_ERR(drvdata->clk); | |
106 | ||
107 | err = clk_prepare_enable(drvdata->clk); | |
108 | if (err) { | |
109 | dev_err(&pdev->dev, "clk enable failed\n"); | |
110 | return err; | |
111 | } | |
112 | ||
113 | clk_rate = clk_get_rate(drvdata->clk); | |
114 | if (!clk_rate) { | |
115 | err = -EINVAL; | |
116 | goto err0; | |
117 | } | |
118 | drvdata->clk_rate = clk_rate; | |
119 | ||
120 | ls1x_wdt = &drvdata->wdt; | |
121 | ls1x_wdt->info = &ls1x_wdt_info; | |
122 | ls1x_wdt->ops = &ls1x_wdt_ops; | |
123 | ls1x_wdt->timeout = DEFAULT_HEARTBEAT; | |
124 | ls1x_wdt->min_timeout = 1; | |
125 | ls1x_wdt->max_hw_heartbeat_ms = U32_MAX / clk_rate * 1000; | |
126 | ls1x_wdt->parent = &pdev->dev; | |
127 | ||
128 | watchdog_init_timeout(ls1x_wdt, heartbeat, &pdev->dev); | |
129 | watchdog_set_nowayout(ls1x_wdt, nowayout); | |
130 | watchdog_set_drvdata(ls1x_wdt, drvdata); | |
131 | ||
132 | err = watchdog_register_device(&drvdata->wdt); | |
133 | if (err) { | |
134 | dev_err(&pdev->dev, "failed to register watchdog device\n"); | |
135 | goto err0; | |
136 | } | |
137 | ||
138 | platform_set_drvdata(pdev, drvdata); | |
139 | ||
140 | dev_info(&pdev->dev, "Loongson1 Watchdog driver registered\n"); | |
141 | ||
142 | return 0; | |
143 | err0: | |
144 | clk_disable_unprepare(drvdata->clk); | |
145 | return err; | |
146 | } | |
147 | ||
148 | static int ls1x_wdt_remove(struct platform_device *pdev) | |
149 | { | |
150 | struct ls1x_wdt_drvdata *drvdata = platform_get_drvdata(pdev); | |
151 | ||
152 | watchdog_unregister_device(&drvdata->wdt); | |
153 | clk_disable_unprepare(drvdata->clk); | |
154 | ||
155 | return 0; | |
156 | } | |
157 | ||
158 | static struct platform_driver ls1x_wdt_driver = { | |
159 | .probe = ls1x_wdt_probe, | |
160 | .remove = ls1x_wdt_remove, | |
161 | .driver = { | |
162 | .name = "ls1x-wdt", | |
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"); |