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(wdd: wdev); |
38 | u32 timeout; |
39 | int err; |
40 | |
41 | err = clk_prepare_enable(clk: priv->clk); |
42 | if (err) |
43 | return err; |
44 | |
45 | timeout = wdev->timeout * clk_get_rate(clk: priv->clk); |
46 | writew(val: timeout & 0xffff, addr: priv->base + REG_WDT_MAX_PRD_L); |
47 | writew(val: (timeout >> 16) & 0xffff, addr: priv->base + REG_WDT_MAX_PRD_H); |
48 | writew(val: 1, addr: 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(wdd: wdev); |
55 | |
56 | writew(val: 1, addr: 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(wdd: wdev); |
63 | |
64 | writew(val: 0, addr: priv->base + REG_WDT_MAX_PRD_L); |
65 | writew(val: 0, addr: priv->base + REG_WDT_MAX_PRD_H); |
66 | writew(val: 0, addr: priv->base + REG_WDT_CLR); |
67 | clk_disable_unprepare(clk: 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(dev: &pdev->dev, size: sizeof(*priv), GFP_KERNEL); |
103 | if (!priv) |
104 | return -ENOMEM; |
105 | |
106 | priv->base = devm_platform_ioremap_resource(pdev, index: 0); |
107 | if (IS_ERR(ptr: priv->base)) |
108 | return PTR_ERR(ptr: priv->base); |
109 | |
110 | priv->clk = devm_clk_get(dev, NULL); |
111 | if (IS_ERR(ptr: priv->clk)) { |
112 | dev_err(dev, "No input clock\n" ); |
113 | return PTR_ERR(ptr: 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(clk: priv->clk); |
121 | priv->wdev.timeout = MSC313E_WDT_DEFAULT_TIMEOUT; |
122 | |
123 | /* If the period is non-zero the WDT is running */ |
124 | if (readw(addr: priv->base + REG_WDT_MAX_PRD_L) | (readw(addr: priv->base + REG_WDT_MAX_PRD_H) << 16)) |
125 | set_bit(WDOG_HW_RUNNING, addr: &priv->wdev.status); |
126 | |
127 | watchdog_set_drvdata(wdd: &priv->wdev, data: priv); |
128 | |
129 | watchdog_init_timeout(wdd: &priv->wdev, timeout_parm: timeout, dev); |
130 | watchdog_stop_on_reboot(wdd: &priv->wdev); |
131 | watchdog_stop_on_unregister(wdd: &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(wdd: &priv->wdev)) |
141 | msc313e_wdt_stop(wdev: &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(wdd: &priv->wdev)) |
151 | msc313e_wdt_start(wdev: &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" ); |
171 | |