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