1 /*
2  * This file is provided under a dual BSD/GPLv2 license.  When using or
3  * redistributing this file, you may do so under either license.
4  *
5  * GPL LICENSE SUMMARY
6  *
7  * Copyright (c) 2016 BayLibre, SAS.
8  * Author: Neil Armstrong <narmstrong@baylibre.com>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of version 2 of the GNU General Public License as
12  * published by the Free Software Foundation.
13  *
14  * This program is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, see <http://www.gnu.org/licenses/>.
21  * The full GNU General Public License is included in this distribution
22  * in the file called COPYING.
23  *
24  * BSD LICENSE
25  *
26  * Copyright (c) 2016 BayLibre, SAS.
27  * Author: Neil Armstrong <narmstrong@baylibre.com>
28  *
29  * Redistribution and use in source and binary forms, with or without
30  * modification, are permitted provided that the following conditions
31  * are met:
32  *
33  *   * Redistributions of source code must retain the above copyright
34  *     notice, this list of conditions and the following disclaimer.
35  *   * Redistributions in binary form must reproduce the above copyright
36  *     notice, this list of conditions and the following disclaimer in
37  *     the documentation and/or other materials provided with the
38  *     distribution.
39  *   * Neither the name of Intel Corporation nor the names of its
40  *     contributors may be used to endorse or promote products derived
41  *     from this software without specific prior written permission.
42  *
43  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
44  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
45  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
46  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
47  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
48  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
49  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
50  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
51  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
52  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
53  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
54  */
55 #include <linux/clk.h>
56 #include <linux/err.h>
57 #include <linux/io.h>
58 #include <linux/module.h>
59 #include <linux/of.h>
60 #include <linux/platform_device.h>
61 #include <linux/slab.h>
62 #include <linux/types.h>
63 #include <linux/watchdog.h>
64 
65 #define DEFAULT_TIMEOUT	30	/* seconds */
66 
67 #define GXBB_WDT_CTRL_REG			0x0
68 #define GXBB_WDT_TCNT_REG			0x8
69 #define GXBB_WDT_RSET_REG			0xc
70 
71 #define GXBB_WDT_CTRL_CLKDIV_EN			BIT(25)
72 #define GXBB_WDT_CTRL_CLK_EN			BIT(24)
73 #define GXBB_WDT_CTRL_EE_RESET			BIT(21)
74 #define GXBB_WDT_CTRL_EN			BIT(18)
75 #define GXBB_WDT_CTRL_DIV_MASK			(BIT(18) - 1)
76 
77 #define GXBB_WDT_TCNT_SETUP_MASK		(BIT(16) - 1)
78 #define GXBB_WDT_TCNT_CNT_SHIFT			16
79 
80 struct meson_gxbb_wdt {
81 	void __iomem *reg_base;
82 	struct watchdog_device wdt_dev;
83 	struct clk *clk;
84 };
85 
86 static int meson_gxbb_wdt_start(struct watchdog_device *wdt_dev)
87 {
88 	struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
89 
90 	writel(readl(data->reg_base + GXBB_WDT_CTRL_REG) | GXBB_WDT_CTRL_EN,
91 	       data->reg_base + GXBB_WDT_CTRL_REG);
92 
93 	return 0;
94 }
95 
96 static int meson_gxbb_wdt_stop(struct watchdog_device *wdt_dev)
97 {
98 	struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
99 
100 	writel(readl(data->reg_base + GXBB_WDT_CTRL_REG) & ~GXBB_WDT_CTRL_EN,
101 	       data->reg_base + GXBB_WDT_CTRL_REG);
102 
103 	return 0;
104 }
105 
106 static int meson_gxbb_wdt_ping(struct watchdog_device *wdt_dev)
107 {
108 	struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
109 
110 	writel(0, data->reg_base + GXBB_WDT_RSET_REG);
111 
112 	return 0;
113 }
114 
115 static int meson_gxbb_wdt_set_timeout(struct watchdog_device *wdt_dev,
116 				      unsigned int timeout)
117 {
118 	struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
119 	unsigned long tcnt = timeout * 1000;
120 
121 	if (tcnt > GXBB_WDT_TCNT_SETUP_MASK)
122 		tcnt = GXBB_WDT_TCNT_SETUP_MASK;
123 
124 	wdt_dev->timeout = timeout;
125 
126 	meson_gxbb_wdt_ping(wdt_dev);
127 
128 	writel(tcnt, data->reg_base + GXBB_WDT_TCNT_REG);
129 
130 	return 0;
131 }
132 
133 static unsigned int meson_gxbb_wdt_get_timeleft(struct watchdog_device *wdt_dev)
134 {
135 	struct meson_gxbb_wdt *data = watchdog_get_drvdata(wdt_dev);
136 	unsigned long reg;
137 
138 	reg = readl(data->reg_base + GXBB_WDT_TCNT_REG);
139 
140 	return ((reg >> GXBB_WDT_TCNT_CNT_SHIFT) -
141 		(reg & GXBB_WDT_TCNT_SETUP_MASK)) / 1000;
142 }
143 
144 static const struct watchdog_ops meson_gxbb_wdt_ops = {
145 	.start = meson_gxbb_wdt_start,
146 	.stop = meson_gxbb_wdt_stop,
147 	.ping = meson_gxbb_wdt_ping,
148 	.set_timeout = meson_gxbb_wdt_set_timeout,
149 	.get_timeleft = meson_gxbb_wdt_get_timeleft,
150 };
151 
152 static const struct watchdog_info meson_gxbb_wdt_info = {
153 	.identity = "Meson GXBB Watchdog",
154 	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
155 };
156 
157 static int __maybe_unused meson_gxbb_wdt_resume(struct device *dev)
158 {
159 	struct meson_gxbb_wdt *data = dev_get_drvdata(dev);
160 
161 	if (watchdog_active(&data->wdt_dev))
162 		meson_gxbb_wdt_start(&data->wdt_dev);
163 
164 	return 0;
165 }
166 
167 static int __maybe_unused meson_gxbb_wdt_suspend(struct device *dev)
168 {
169 	struct meson_gxbb_wdt *data = dev_get_drvdata(dev);
170 
171 	if (watchdog_active(&data->wdt_dev))
172 		meson_gxbb_wdt_stop(&data->wdt_dev);
173 
174 	return 0;
175 }
176 
177 static const struct dev_pm_ops meson_gxbb_wdt_pm_ops = {
178 	SET_SYSTEM_SLEEP_PM_OPS(meson_gxbb_wdt_suspend, meson_gxbb_wdt_resume)
179 };
180 
181 static const struct of_device_id meson_gxbb_wdt_dt_ids[] = {
182 	 { .compatible = "amlogic,meson-gxbb-wdt", },
183 	 { /* sentinel */ },
184 };
185 MODULE_DEVICE_TABLE(of, meson_gxbb_wdt_dt_ids);
186 
187 static int meson_gxbb_wdt_probe(struct platform_device *pdev)
188 {
189 	struct meson_gxbb_wdt *data;
190 	struct resource *res;
191 	int ret;
192 
193 	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
194 	if (!data)
195 		return -ENOMEM;
196 
197 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
198 	data->reg_base = devm_ioremap_resource(&pdev->dev, res);
199 	if (IS_ERR(data->reg_base))
200 		return PTR_ERR(data->reg_base);
201 
202 	data->clk = devm_clk_get(&pdev->dev, NULL);
203 	if (IS_ERR(data->clk))
204 		return PTR_ERR(data->clk);
205 
206 	ret = clk_prepare_enable(data->clk);
207 	if (ret)
208 		return ret;
209 
210 	platform_set_drvdata(pdev, data);
211 
212 	data->wdt_dev.parent = &pdev->dev;
213 	data->wdt_dev.info = &meson_gxbb_wdt_info;
214 	data->wdt_dev.ops = &meson_gxbb_wdt_ops;
215 	data->wdt_dev.max_hw_heartbeat_ms = GXBB_WDT_TCNT_SETUP_MASK;
216 	data->wdt_dev.min_timeout = 1;
217 	data->wdt_dev.timeout = DEFAULT_TIMEOUT;
218 	watchdog_set_drvdata(&data->wdt_dev, data);
219 
220 	/* Setup with 1ms timebase */
221 	writel(((clk_get_rate(data->clk) / 1000) & GXBB_WDT_CTRL_DIV_MASK) |
222 		GXBB_WDT_CTRL_EE_RESET |
223 		GXBB_WDT_CTRL_CLK_EN |
224 		GXBB_WDT_CTRL_CLKDIV_EN,
225 		data->reg_base + GXBB_WDT_CTRL_REG);
226 
227 	meson_gxbb_wdt_set_timeout(&data->wdt_dev, data->wdt_dev.timeout);
228 
229 	ret = watchdog_register_device(&data->wdt_dev);
230 	if (ret) {
231 		clk_disable_unprepare(data->clk);
232 		return ret;
233 	}
234 
235 	return 0;
236 }
237 
238 static int meson_gxbb_wdt_remove(struct platform_device *pdev)
239 {
240 	struct meson_gxbb_wdt *data = platform_get_drvdata(pdev);
241 
242 	watchdog_unregister_device(&data->wdt_dev);
243 
244 	clk_disable_unprepare(data->clk);
245 
246 	return 0;
247 }
248 
249 static void meson_gxbb_wdt_shutdown(struct platform_device *pdev)
250 {
251 	struct meson_gxbb_wdt *data = platform_get_drvdata(pdev);
252 
253 	meson_gxbb_wdt_stop(&data->wdt_dev);
254 }
255 
256 static struct platform_driver meson_gxbb_wdt_driver = {
257 	.probe	= meson_gxbb_wdt_probe,
258 	.remove	= meson_gxbb_wdt_remove,
259 	.shutdown = meson_gxbb_wdt_shutdown,
260 	.driver = {
261 		.name = "meson-gxbb-wdt",
262 		.pm = &meson_gxbb_wdt_pm_ops,
263 		.of_match_table	= meson_gxbb_wdt_dt_ids,
264 	},
265 };
266 
267 module_platform_driver(meson_gxbb_wdt_driver);
268 
269 MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
270 MODULE_DESCRIPTION("Amlogic Meson GXBB Watchdog timer driver");
271 MODULE_LICENSE("Dual BSD/GPL");
272