Commit | Line | Data |
---|---|---|
e9800b79 DP |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * MStar WDT driver | |
4 | * | |
5 | * Copyright (C) 2019 - 2021 Daniel Palmer | |
6 | * Copyright (C) 2021 Romain Perier | |
7 | * | |
8 | */ | |
9 | ||
10 | #include <linux/clk.h> | |
11 | #include <linux/io.h> | |
12 | #include <linux/mod_devicetable.h> | |
13 | #include <linux/module.h> | |
14 | #include <linux/platform_device.h> | |
15 | #include <linux/watchdog.h> | |
16 | ||
17 | #define REG_WDT_CLR 0x0 | |
18 | #define REG_WDT_MAX_PRD_L 0x10 | |
19 | #define REG_WDT_MAX_PRD_H 0x14 | |
20 | ||
21 | #define MSC313E_WDT_MIN_TIMEOUT 1 | |
22 | #define MSC313E_WDT_DEFAULT_TIMEOUT 30 | |
23 | ||
24 | static unsigned int timeout; | |
25 | ||
26 | module_param(timeout, int, 0); | |
27 | MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds"); | |
28 | ||
29 | struct msc313e_wdt_priv { | |
30 | void __iomem *base; | |
31 | struct watchdog_device wdev; | |
32 | struct clk *clk; | |
33 | }; | |
34 | ||
35 | static int msc313e_wdt_start(struct watchdog_device *wdev) | |
36 | { | |
37 | struct msc313e_wdt_priv *priv = watchdog_get_drvdata(wdev); | |
38 | u32 timeout; | |
39 | int err; | |
40 | ||
41 | err = clk_prepare_enable(priv->clk); | |
42 | if (err) | |
43 | return err; | |
44 | ||
45 | timeout = wdev->timeout * clk_get_rate(priv->clk); | |
46 | writew(timeout & 0xffff, priv->base + REG_WDT_MAX_PRD_L); | |
47 | writew((timeout >> 16) & 0xffff, priv->base + REG_WDT_MAX_PRD_H); | |
48 | writew(1, priv->base + REG_WDT_CLR); | |
49 | return 0; | |
50 | } | |
51 | ||
52 | static int msc313e_wdt_ping(struct watchdog_device *wdev) | |
53 | { | |
54 | struct msc313e_wdt_priv *priv = watchdog_get_drvdata(wdev); | |
55 | ||
56 | writew(1, priv->base + REG_WDT_CLR); | |
57 | return 0; | |
58 | } | |
59 | ||
60 | static int msc313e_wdt_stop(struct watchdog_device *wdev) | |
61 | { | |
62 | struct msc313e_wdt_priv *priv = watchdog_get_drvdata(wdev); | |
63 | ||
64 | writew(0, priv->base + REG_WDT_MAX_PRD_L); | |
65 | writew(0, priv->base + REG_WDT_MAX_PRD_H); | |
66 | writew(0, priv->base + REG_WDT_CLR); | |
67 | clk_disable_unprepare(priv->clk); | |
68 | return 0; | |
69 | } | |
70 | ||
71 | static int msc313e_wdt_settimeout(struct watchdog_device *wdev, unsigned int new_time) | |
72 | { | |
73 | wdev->timeout = new_time; | |
74 | ||
75 | return msc313e_wdt_start(wdev); | |
76 | } | |
77 | ||
78 | static const struct watchdog_info msc313e_wdt_ident = { | |
79 | .identity = "MSC313e watchdog", | |
80 | .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT, | |
81 | }; | |
82 | ||
83 | static const struct watchdog_ops msc313e_wdt_ops = { | |
84 | .owner = THIS_MODULE, | |
85 | .start = msc313e_wdt_start, | |
86 | .stop = msc313e_wdt_stop, | |
87 | .ping = msc313e_wdt_ping, | |
88 | .set_timeout = msc313e_wdt_settimeout, | |
89 | }; | |
90 | ||
91 | static const struct of_device_id msc313e_wdt_of_match[] = { | |
92 | { .compatible = "mstar,msc313e-wdt", }, | |
93 | { /* sentinel */ } | |
94 | }; | |
95 | MODULE_DEVICE_TABLE(of, msc313e_wdt_of_match); | |
96 | ||
97 | static int msc313e_wdt_probe(struct platform_device *pdev) | |
98 | { | |
99 | struct device *dev = &pdev->dev; | |
100 | struct msc313e_wdt_priv *priv; | |
101 | ||
102 | priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); | |
103 | if (!priv) | |
104 | return -ENOMEM; | |
105 | ||
106 | priv->base = devm_platform_ioremap_resource(pdev, 0); | |
107 | if (IS_ERR(priv->base)) | |
108 | return PTR_ERR(priv->base); | |
109 | ||
110 | priv->clk = devm_clk_get(dev, NULL); | |
111 | if (IS_ERR(priv->clk)) { | |
112 | dev_err(dev, "No input clock\n"); | |
113 | return PTR_ERR(priv->clk); | |
114 | } | |
115 | ||
116 | priv->wdev.info = &msc313e_wdt_ident, | |
117 | priv->wdev.ops = &msc313e_wdt_ops, | |
118 | priv->wdev.parent = dev; | |
119 | priv->wdev.min_timeout = MSC313E_WDT_MIN_TIMEOUT; | |
120 | priv->wdev.max_timeout = U32_MAX / clk_get_rate(priv->clk); | |
121 | priv->wdev.timeout = MSC313E_WDT_DEFAULT_TIMEOUT; | |
122 | ||
ffd264bd DP |
123 | /* If the period is non-zero the WDT is running */ |
124 | if (readw(priv->base + REG_WDT_MAX_PRD_L) | (readw(priv->base + REG_WDT_MAX_PRD_H) << 16)) | |
125 | set_bit(WDOG_HW_RUNNING, &priv->wdev.status); | |
126 | ||
e9800b79 DP |
127 | watchdog_set_drvdata(&priv->wdev, priv); |
128 | ||
129 | watchdog_init_timeout(&priv->wdev, timeout, dev); | |
130 | watchdog_stop_on_reboot(&priv->wdev); | |
131 | watchdog_stop_on_unregister(&priv->wdev); | |
132 | ||
133 | return devm_watchdog_register_device(dev, &priv->wdev); | |
134 | } | |
135 | ||
136 | static int __maybe_unused msc313e_wdt_suspend(struct device *dev) | |
137 | { | |
138 | struct msc313e_wdt_priv *priv = dev_get_drvdata(dev); | |
139 | ||
140 | if (watchdog_active(&priv->wdev)) | |
141 | msc313e_wdt_stop(&priv->wdev); | |
142 | ||
143 | return 0; | |
144 | } | |
145 | ||
146 | static int __maybe_unused msc313e_wdt_resume(struct device *dev) | |
147 | { | |
148 | struct msc313e_wdt_priv *priv = dev_get_drvdata(dev); | |
149 | ||
150 | if (watchdog_active(&priv->wdev)) | |
151 | msc313e_wdt_start(&priv->wdev); | |
152 | ||
153 | return 0; | |
154 | } | |
155 | ||
156 | static SIMPLE_DEV_PM_OPS(msc313e_wdt_pm_ops, msc313e_wdt_suspend, msc313e_wdt_resume); | |
157 | ||
158 | static struct platform_driver msc313e_wdt_driver = { | |
159 | .driver = { | |
160 | .name = "msc313e-wdt", | |
161 | .of_match_table = msc313e_wdt_of_match, | |
162 | .pm = &msc313e_wdt_pm_ops, | |
163 | }, | |
164 | .probe = msc313e_wdt_probe, | |
165 | }; | |
166 | module_platform_driver(msc313e_wdt_driver); | |
167 | ||
168 | MODULE_AUTHOR("Daniel Palmer <daniel@thingy.jp>"); | |
169 | MODULE_DESCRIPTION("Watchdog driver for MStar MSC313e"); | |
170 | MODULE_LICENSE("GPL v2"); |