Merge tag 'armsoc-dt64' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc
[cascardo/linux.git] / drivers / watchdog / meson_gxbb_wdt.c
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         clk_prepare_enable(data->clk);
207
208         platform_set_drvdata(pdev, data);
209
210         data->wdt_dev.parent = &pdev->dev;
211         data->wdt_dev.info = &meson_gxbb_wdt_info;
212         data->wdt_dev.ops = &meson_gxbb_wdt_ops;
213         data->wdt_dev.max_hw_heartbeat_ms = GXBB_WDT_TCNT_SETUP_MASK;
214         data->wdt_dev.min_timeout = 1;
215         data->wdt_dev.timeout = DEFAULT_TIMEOUT;
216         watchdog_set_drvdata(&data->wdt_dev, data);
217
218         /* Setup with 1ms timebase */
219         writel(((clk_get_rate(data->clk) / 1000) & GXBB_WDT_CTRL_DIV_MASK) |
220                 GXBB_WDT_CTRL_EE_RESET |
221                 GXBB_WDT_CTRL_CLK_EN |
222                 GXBB_WDT_CTRL_CLKDIV_EN,
223                 data->reg_base + GXBB_WDT_CTRL_REG);
224
225         meson_gxbb_wdt_set_timeout(&data->wdt_dev, data->wdt_dev.timeout);
226
227         ret = watchdog_register_device(&data->wdt_dev);
228         if (ret) {
229                 clk_disable_unprepare(data->clk);
230                 return ret;
231         }
232
233         return 0;
234 }
235
236 static int meson_gxbb_wdt_remove(struct platform_device *pdev)
237 {
238         struct meson_gxbb_wdt *data = platform_get_drvdata(pdev);
239
240         watchdog_unregister_device(&data->wdt_dev);
241
242         clk_disable_unprepare(data->clk);
243
244         return 0;
245 }
246
247 static void meson_gxbb_wdt_shutdown(struct platform_device *pdev)
248 {
249         struct meson_gxbb_wdt *data = platform_get_drvdata(pdev);
250
251         meson_gxbb_wdt_stop(&data->wdt_dev);
252 }
253
254 static struct platform_driver meson_gxbb_wdt_driver = {
255         .probe  = meson_gxbb_wdt_probe,
256         .remove = meson_gxbb_wdt_remove,
257         .shutdown = meson_gxbb_wdt_shutdown,
258         .driver = {
259                 .name = "meson-gxbb-wdt",
260                 .pm = &meson_gxbb_wdt_pm_ops,
261                 .of_match_table = meson_gxbb_wdt_dt_ids,
262         },
263 };
264
265 module_platform_driver(meson_gxbb_wdt_driver);
266
267 MODULE_ALIAS("platform:meson-gxbb-wdt");
268 MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
269 MODULE_DESCRIPTION("Amlogic Meson GXBB Watchdog timer driver");
270 MODULE_LICENSE("Dual BSD/GPL");