1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Watchdog driver for the SA11x0/PXA2xx |
4 | * |
5 | * (c) Copyright 2000 Oleg Drokin <green@crimea.edu> |
6 | * Based on SoftDog driver by Alan Cox <alan@lxorguk.ukuu.org.uk> |
7 | * |
8 | * Neither Oleg Drokin nor iXcelerator.com admit liability nor provide |
9 | * warranty for any of this software. This material is provided |
10 | * "AS-IS" and at no charge. |
11 | * |
12 | * (c) Copyright 2000 Oleg Drokin <green@crimea.edu> |
13 | * |
14 | * 27/11/2000 Initial release |
15 | */ |
16 | |
17 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
18 | |
19 | #include <linux/module.h> |
20 | #include <linux/moduleparam.h> |
21 | #include <linux/clk.h> |
22 | #include <linux/types.h> |
23 | #include <linux/kernel.h> |
24 | #include <linux/fs.h> |
25 | #include <linux/platform_device.h> |
26 | #include <linux/miscdevice.h> |
27 | #include <linux/watchdog.h> |
28 | #include <linux/init.h> |
29 | #include <linux/io.h> |
30 | #include <linux/bitops.h> |
31 | #include <linux/uaccess.h> |
32 | #include <linux/timex.h> |
33 | |
34 | #define REG_OSMR0 0x0000 /* OS timer Match Reg. 0 */ |
35 | #define REG_OSMR1 0x0004 /* OS timer Match Reg. 1 */ |
36 | #define REG_OSMR2 0x0008 /* OS timer Match Reg. 2 */ |
37 | #define REG_OSMR3 0x000c /* OS timer Match Reg. 3 */ |
38 | #define REG_OSCR 0x0010 /* OS timer Counter Reg. */ |
39 | #define REG_OSSR 0x0014 /* OS timer Status Reg. */ |
40 | #define REG_OWER 0x0018 /* OS timer Watch-dog Enable Reg. */ |
41 | #define REG_OIER 0x001C /* OS timer Interrupt Enable Reg. */ |
42 | |
43 | #define OSSR_M3 (1 << 3) /* Match status channel 3 */ |
44 | #define OSSR_M2 (1 << 2) /* Match status channel 2 */ |
45 | #define OSSR_M1 (1 << 1) /* Match status channel 1 */ |
46 | #define OSSR_M0 (1 << 0) /* Match status channel 0 */ |
47 | |
48 | #define OWER_WME (1 << 0) /* Watchdog Match Enable */ |
49 | |
50 | #define OIER_E3 (1 << 3) /* Interrupt enable channel 3 */ |
51 | #define OIER_E2 (1 << 2) /* Interrupt enable channel 2 */ |
52 | #define OIER_E1 (1 << 1) /* Interrupt enable channel 1 */ |
53 | #define OIER_E0 (1 << 0) /* Interrupt enable channel 0 */ |
54 | |
55 | static unsigned long oscr_freq; |
56 | static unsigned long sa1100wdt_users; |
57 | static unsigned int pre_margin; |
58 | static int boot_status; |
59 | static void __iomem *reg_base; |
60 | |
61 | static inline void sa1100_wr(u32 val, u32 offset) |
62 | { |
63 | writel_relaxed(val, reg_base + offset); |
64 | } |
65 | |
66 | static inline u32 sa1100_rd(u32 offset) |
67 | { |
68 | return readl_relaxed(reg_base + offset); |
69 | } |
70 | |
71 | /* |
72 | * Allow only one person to hold it open |
73 | */ |
74 | static int sa1100dog_open(struct inode *inode, struct file *file) |
75 | { |
76 | if (test_and_set_bit(nr: 1, addr: &sa1100wdt_users)) |
77 | return -EBUSY; |
78 | |
79 | /* Activate SA1100 Watchdog timer */ |
80 | sa1100_wr(val: sa1100_rd(REG_OSCR) + pre_margin, REG_OSMR3); |
81 | sa1100_wr(OSSR_M3, REG_OSSR); |
82 | sa1100_wr(OWER_WME, REG_OWER); |
83 | sa1100_wr(val: sa1100_rd(REG_OIER) | OIER_E3, REG_OIER); |
84 | return stream_open(inode, filp: file); |
85 | } |
86 | |
87 | /* |
88 | * The watchdog cannot be disabled. |
89 | * |
90 | * Previous comments suggested that turning off the interrupt by |
91 | * clearing REG_OIER[E3] would prevent the watchdog timing out but this |
92 | * does not appear to be true (at least on the PXA255). |
93 | */ |
94 | static int sa1100dog_release(struct inode *inode, struct file *file) |
95 | { |
96 | pr_crit("Device closed - timer will not stop\n" ); |
97 | clear_bit(nr: 1, addr: &sa1100wdt_users); |
98 | return 0; |
99 | } |
100 | |
101 | static ssize_t sa1100dog_write(struct file *file, const char __user *data, |
102 | size_t len, loff_t *ppos) |
103 | { |
104 | if (len) |
105 | /* Refresh OSMR3 timer. */ |
106 | sa1100_wr(val: sa1100_rd(REG_OSCR) + pre_margin, REG_OSMR3); |
107 | return len; |
108 | } |
109 | |
110 | static const struct watchdog_info ident = { |
111 | .options = WDIOF_CARDRESET | WDIOF_SETTIMEOUT |
112 | | WDIOF_KEEPALIVEPING, |
113 | .identity = "SA1100/PXA255 Watchdog" , |
114 | .firmware_version = 1, |
115 | }; |
116 | |
117 | static long sa1100dog_ioctl(struct file *file, unsigned int cmd, |
118 | unsigned long arg) |
119 | { |
120 | int ret = -ENOTTY; |
121 | int time; |
122 | void __user *argp = (void __user *)arg; |
123 | int __user *p = argp; |
124 | |
125 | switch (cmd) { |
126 | case WDIOC_GETSUPPORT: |
127 | ret = copy_to_user(to: argp, from: &ident, |
128 | n: sizeof(ident)) ? -EFAULT : 0; |
129 | break; |
130 | |
131 | case WDIOC_GETSTATUS: |
132 | ret = put_user(0, p); |
133 | break; |
134 | |
135 | case WDIOC_GETBOOTSTATUS: |
136 | ret = put_user(boot_status, p); |
137 | break; |
138 | |
139 | case WDIOC_KEEPALIVE: |
140 | sa1100_wr(val: sa1100_rd(REG_OSCR) + pre_margin, REG_OSMR3); |
141 | ret = 0; |
142 | break; |
143 | |
144 | case WDIOC_SETTIMEOUT: |
145 | ret = get_user(time, p); |
146 | if (ret) |
147 | break; |
148 | |
149 | if (time <= 0 || (oscr_freq * (long long)time >= 0xffffffff)) { |
150 | ret = -EINVAL; |
151 | break; |
152 | } |
153 | |
154 | pre_margin = oscr_freq * time; |
155 | sa1100_wr(val: sa1100_rd(REG_OSCR) + pre_margin, REG_OSMR3); |
156 | fallthrough; |
157 | |
158 | case WDIOC_GETTIMEOUT: |
159 | ret = put_user(pre_margin / oscr_freq, p); |
160 | break; |
161 | } |
162 | return ret; |
163 | } |
164 | |
165 | static const struct file_operations sa1100dog_fops = { |
166 | .owner = THIS_MODULE, |
167 | .llseek = no_llseek, |
168 | .write = sa1100dog_write, |
169 | .unlocked_ioctl = sa1100dog_ioctl, |
170 | .compat_ioctl = compat_ptr_ioctl, |
171 | .open = sa1100dog_open, |
172 | .release = sa1100dog_release, |
173 | }; |
174 | |
175 | static struct miscdevice sa1100dog_miscdev = { |
176 | .minor = WATCHDOG_MINOR, |
177 | .name = "watchdog" , |
178 | .fops = &sa1100dog_fops, |
179 | }; |
180 | |
181 | static int margin = 60; /* (secs) Default is 1 minute */ |
182 | static struct clk *clk; |
183 | |
184 | static int sa1100dog_probe(struct platform_device *pdev) |
185 | { |
186 | int ret; |
187 | int *platform_data; |
188 | struct resource *res; |
189 | |
190 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
191 | if (!res) |
192 | return -ENXIO; |
193 | reg_base = devm_ioremap(dev: &pdev->dev, offset: res->start, size: resource_size(res)); |
194 | ret = PTR_ERR_OR_ZERO(ptr: reg_base); |
195 | if (ret) |
196 | return ret; |
197 | |
198 | clk = clk_get(NULL, id: "OSTIMER0" ); |
199 | if (IS_ERR(ptr: clk)) { |
200 | pr_err("SA1100/PXA2xx Watchdog Timer: clock not found: %d\n" , |
201 | (int) PTR_ERR(clk)); |
202 | return PTR_ERR(ptr: clk); |
203 | } |
204 | |
205 | ret = clk_prepare_enable(clk); |
206 | if (ret) { |
207 | pr_err("SA1100/PXA2xx Watchdog Timer: clock failed to prepare+enable: %d\n" , |
208 | ret); |
209 | goto err; |
210 | } |
211 | |
212 | oscr_freq = clk_get_rate(clk); |
213 | |
214 | platform_data = pdev->dev.platform_data; |
215 | if (platform_data && *platform_data) |
216 | boot_status = WDIOF_CARDRESET; |
217 | pre_margin = oscr_freq * margin; |
218 | |
219 | ret = misc_register(misc: &sa1100dog_miscdev); |
220 | if (ret == 0) { |
221 | pr_info("SA1100/PXA2xx Watchdog Timer: timer margin %d sec\n" , |
222 | margin); |
223 | return 0; |
224 | } |
225 | |
226 | clk_disable_unprepare(clk); |
227 | err: |
228 | clk_put(clk); |
229 | return ret; |
230 | } |
231 | |
232 | static void sa1100dog_remove(struct platform_device *pdev) |
233 | { |
234 | misc_deregister(misc: &sa1100dog_miscdev); |
235 | clk_disable_unprepare(clk); |
236 | clk_put(clk); |
237 | } |
238 | |
239 | static struct platform_driver sa1100dog_driver = { |
240 | .driver.name = "sa1100_wdt" , |
241 | .probe = sa1100dog_probe, |
242 | .remove_new = sa1100dog_remove, |
243 | }; |
244 | module_platform_driver(sa1100dog_driver); |
245 | |
246 | MODULE_AUTHOR("Oleg Drokin <green@crimea.edu>" ); |
247 | MODULE_DESCRIPTION("SA1100/PXA2xx Watchdog" ); |
248 | |
249 | module_param(margin, int, 0); |
250 | MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)" ); |
251 | |
252 | MODULE_LICENSE("GPL" ); |
253 | |