Commit | Line | Data |
---|---|---|
2bdf6acb AF |
1 | /* |
2 | * Realtek RTD129x watchdog | |
3 | * | |
4 | * Copyright (c) 2017 Andreas Färber | |
5 | * | |
6 | * SPDX-License-Identifier: GPL-2.0+ | |
7 | */ | |
8 | ||
9 | #include <linux/bitops.h> | |
10 | #include <linux/clk.h> | |
11 | #include <linux/io.h> | |
2bdf6acb AF |
12 | #include <linux/of.h> |
13 | #include <linux/of_address.h> | |
14 | #include <linux/platform_device.h> | |
15 | #include <linux/watchdog.h> | |
16 | ||
17 | #define RTD119X_TCWCR 0x0 | |
18 | #define RTD119X_TCWTR 0x4 | |
19 | #define RTD119X_TCWOV 0xc | |
20 | ||
21 | #define RTD119X_TCWCR_WDEN_DISABLED 0xa5 | |
22 | #define RTD119X_TCWCR_WDEN_ENABLED 0xff | |
23 | #define RTD119X_TCWCR_WDEN_MASK 0xff | |
24 | ||
25 | #define RTD119X_TCWTR_WDCLR BIT(0) | |
26 | ||
27 | struct rtd119x_watchdog_device { | |
28 | struct watchdog_device wdt_dev; | |
29 | void __iomem *base; | |
30 | struct clk *clk; | |
31 | }; | |
32 | ||
33 | static int rtd119x_wdt_start(struct watchdog_device *wdev) | |
34 | { | |
35 | struct rtd119x_watchdog_device *data = watchdog_get_drvdata(wdev); | |
36 | u32 val; | |
37 | ||
38 | val = readl_relaxed(data->base + RTD119X_TCWCR); | |
39 | val &= ~RTD119X_TCWCR_WDEN_MASK; | |
40 | val |= RTD119X_TCWCR_WDEN_ENABLED; | |
41 | writel(val, data->base + RTD119X_TCWCR); | |
42 | ||
43 | return 0; | |
44 | } | |
45 | ||
46 | static int rtd119x_wdt_stop(struct watchdog_device *wdev) | |
47 | { | |
48 | struct rtd119x_watchdog_device *data = watchdog_get_drvdata(wdev); | |
49 | u32 val; | |
50 | ||
51 | val = readl_relaxed(data->base + RTD119X_TCWCR); | |
52 | val &= ~RTD119X_TCWCR_WDEN_MASK; | |
53 | val |= RTD119X_TCWCR_WDEN_DISABLED; | |
54 | writel(val, data->base + RTD119X_TCWCR); | |
55 | ||
56 | return 0; | |
57 | } | |
58 | ||
59 | static int rtd119x_wdt_ping(struct watchdog_device *wdev) | |
60 | { | |
61 | struct rtd119x_watchdog_device *data = watchdog_get_drvdata(wdev); | |
62 | ||
63 | writel_relaxed(RTD119X_TCWTR_WDCLR, data->base + RTD119X_TCWTR); | |
64 | ||
65 | return rtd119x_wdt_start(wdev); | |
66 | } | |
67 | ||
68 | static int rtd119x_wdt_set_timeout(struct watchdog_device *wdev, unsigned int val) | |
69 | { | |
70 | struct rtd119x_watchdog_device *data = watchdog_get_drvdata(wdev); | |
71 | ||
72 | writel(val * clk_get_rate(data->clk), data->base + RTD119X_TCWOV); | |
73 | ||
74 | data->wdt_dev.timeout = val; | |
75 | ||
76 | return 0; | |
77 | } | |
78 | ||
79 | static const struct watchdog_ops rtd119x_wdt_ops = { | |
80 | .owner = THIS_MODULE, | |
81 | .start = rtd119x_wdt_start, | |
82 | .stop = rtd119x_wdt_stop, | |
83 | .ping = rtd119x_wdt_ping, | |
84 | .set_timeout = rtd119x_wdt_set_timeout, | |
85 | }; | |
86 | ||
87 | static const struct watchdog_info rtd119x_wdt_info = { | |
88 | .identity = "rtd119x-wdt", | |
89 | .options = 0, | |
90 | }; | |
91 | ||
92 | static const struct of_device_id rtd119x_wdt_dt_ids[] = { | |
93 | { .compatible = "realtek,rtd1295-watchdog" }, | |
94 | { } | |
95 | }; | |
96 | ||
553140a0 GR |
97 | static void rtd119x_clk_disable_unprepare(void *data) |
98 | { | |
99 | clk_disable_unprepare(data); | |
100 | } | |
101 | ||
2bdf6acb AF |
102 | static int rtd119x_wdt_probe(struct platform_device *pdev) |
103 | { | |
553140a0 | 104 | struct device *dev = &pdev->dev; |
2bdf6acb | 105 | struct rtd119x_watchdog_device *data; |
2bdf6acb AF |
106 | int ret; |
107 | ||
553140a0 | 108 | data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); |
2bdf6acb AF |
109 | if (!data) |
110 | return -ENOMEM; | |
111 | ||
0f0a6a28 | 112 | data->base = devm_platform_ioremap_resource(pdev, 0); |
2bdf6acb AF |
113 | if (IS_ERR(data->base)) |
114 | return PTR_ERR(data->base); | |
115 | ||
553140a0 | 116 | data->clk = devm_clk_get(dev, NULL); |
2bdf6acb AF |
117 | if (IS_ERR(data->clk)) |
118 | return PTR_ERR(data->clk); | |
119 | ||
120 | ret = clk_prepare_enable(data->clk); | |
553140a0 GR |
121 | if (ret) |
122 | return ret; | |
123 | ret = devm_add_action_or_reset(dev, rtd119x_clk_disable_unprepare, | |
124 | data->clk); | |
125 | if (ret) | |
2bdf6acb | 126 | return ret; |
2bdf6acb AF |
127 | |
128 | data->wdt_dev.info = &rtd119x_wdt_info; | |
129 | data->wdt_dev.ops = &rtd119x_wdt_ops; | |
130 | data->wdt_dev.timeout = 120; | |
131 | data->wdt_dev.max_timeout = 0xffffffff / clk_get_rate(data->clk); | |
132 | data->wdt_dev.min_timeout = 1; | |
553140a0 | 133 | data->wdt_dev.parent = dev; |
2bdf6acb AF |
134 | |
135 | watchdog_stop_on_reboot(&data->wdt_dev); | |
136 | watchdog_set_drvdata(&data->wdt_dev, data); | |
137 | platform_set_drvdata(pdev, data); | |
138 | ||
139 | writel_relaxed(RTD119X_TCWTR_WDCLR, data->base + RTD119X_TCWTR); | |
140 | rtd119x_wdt_set_timeout(&data->wdt_dev, data->wdt_dev.timeout); | |
141 | rtd119x_wdt_stop(&data->wdt_dev); | |
142 | ||
553140a0 | 143 | return devm_watchdog_register_device(dev, &data->wdt_dev); |
2bdf6acb AF |
144 | } |
145 | ||
146 | static struct platform_driver rtd119x_wdt_driver = { | |
147 | .probe = rtd119x_wdt_probe, | |
2bdf6acb AF |
148 | .driver = { |
149 | .name = "rtd1295-watchdog", | |
150 | .of_match_table = rtd119x_wdt_dt_ids, | |
151 | }, | |
152 | }; | |
153 | builtin_platform_driver(rtd119x_wdt_driver); |