1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Meson Watchdog Driver 4 * 5 * Copyright (c) 2014 Carlo Caione 6 */ 7 8 #include <linux/clk.h> 9 #include <linux/delay.h> 10 #include <linux/err.h> 11 #include <linux/init.h> 12 #include <linux/io.h> 13 #include <linux/kernel.h> 14 #include <linux/module.h> 15 #include <linux/moduleparam.h> 16 #include <linux/of.h> 17 #include <linux/of_device.h> 18 #include <linux/platform_device.h> 19 #include <linux/types.h> 20 #include <linux/watchdog.h> 21 22 #define DRV_NAME "meson_wdt" 23 24 #define MESON_WDT_TC 0x00 25 #define MESON_WDT_DC_RESET (3 << 24) 26 27 #define MESON_WDT_RESET 0x04 28 29 #define MESON_WDT_TIMEOUT 30 30 #define MESON_WDT_MIN_TIMEOUT 1 31 32 #define MESON_SEC_TO_TC(s, c) ((s) * (c)) 33 34 static bool nowayout = WATCHDOG_NOWAYOUT; 35 static unsigned int timeout; 36 37 struct meson_wdt_data { 38 unsigned int enable; 39 unsigned int terminal_count_mask; 40 unsigned int count_unit; 41 }; 42 43 static struct meson_wdt_data meson6_wdt_data = { 44 .enable = BIT(22), 45 .terminal_count_mask = 0x3fffff, 46 .count_unit = 100000, /* 10 us */ 47 }; 48 49 static struct meson_wdt_data meson8b_wdt_data = { 50 .enable = BIT(19), 51 .terminal_count_mask = 0xffff, 52 .count_unit = 7812, /* 128 us */ 53 }; 54 55 struct meson_wdt_dev { 56 struct watchdog_device wdt_dev; 57 void __iomem *wdt_base; 58 const struct meson_wdt_data *data; 59 }; 60 61 static int meson_wdt_restart(struct watchdog_device *wdt_dev, 62 unsigned long action, void *data) 63 { 64 struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); 65 u32 tc_reboot = MESON_WDT_DC_RESET; 66 67 tc_reboot |= meson_wdt->data->enable; 68 69 while (1) { 70 writel(tc_reboot, meson_wdt->wdt_base + MESON_WDT_TC); 71 mdelay(5); 72 } 73 74 return 0; 75 } 76 77 static int meson_wdt_ping(struct watchdog_device *wdt_dev) 78 { 79 struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); 80 81 writel(0, meson_wdt->wdt_base + MESON_WDT_RESET); 82 83 return 0; 84 } 85 86 static void meson_wdt_change_timeout(struct watchdog_device *wdt_dev, 87 unsigned int timeout) 88 { 89 struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); 90 u32 reg; 91 92 reg = readl(meson_wdt->wdt_base + MESON_WDT_TC); 93 reg &= ~meson_wdt->data->terminal_count_mask; 94 reg |= MESON_SEC_TO_TC(timeout, meson_wdt->data->count_unit); 95 writel(reg, meson_wdt->wdt_base + MESON_WDT_TC); 96 } 97 98 static int meson_wdt_set_timeout(struct watchdog_device *wdt_dev, 99 unsigned int timeout) 100 { 101 wdt_dev->timeout = timeout; 102 103 meson_wdt_change_timeout(wdt_dev, timeout); 104 meson_wdt_ping(wdt_dev); 105 106 return 0; 107 } 108 109 static int meson_wdt_stop(struct watchdog_device *wdt_dev) 110 { 111 struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); 112 u32 reg; 113 114 reg = readl(meson_wdt->wdt_base + MESON_WDT_TC); 115 reg &= ~meson_wdt->data->enable; 116 writel(reg, meson_wdt->wdt_base + MESON_WDT_TC); 117 118 return 0; 119 } 120 121 static int meson_wdt_start(struct watchdog_device *wdt_dev) 122 { 123 struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); 124 u32 reg; 125 126 meson_wdt_change_timeout(wdt_dev, meson_wdt->wdt_dev.timeout); 127 meson_wdt_ping(wdt_dev); 128 129 reg = readl(meson_wdt->wdt_base + MESON_WDT_TC); 130 reg |= meson_wdt->data->enable; 131 writel(reg, meson_wdt->wdt_base + MESON_WDT_TC); 132 133 return 0; 134 } 135 136 static const struct watchdog_info meson_wdt_info = { 137 .identity = DRV_NAME, 138 .options = WDIOF_SETTIMEOUT | 139 WDIOF_KEEPALIVEPING | 140 WDIOF_MAGICCLOSE, 141 }; 142 143 static const struct watchdog_ops meson_wdt_ops = { 144 .owner = THIS_MODULE, 145 .start = meson_wdt_start, 146 .stop = meson_wdt_stop, 147 .ping = meson_wdt_ping, 148 .set_timeout = meson_wdt_set_timeout, 149 .restart = meson_wdt_restart, 150 }; 151 152 static const struct of_device_id meson_wdt_dt_ids[] = { 153 { .compatible = "amlogic,meson6-wdt", .data = &meson6_wdt_data }, 154 { .compatible = "amlogic,meson8-wdt", .data = &meson6_wdt_data }, 155 { .compatible = "amlogic,meson8b-wdt", .data = &meson8b_wdt_data }, 156 { .compatible = "amlogic,meson8m2-wdt", .data = &meson8b_wdt_data }, 157 { /* sentinel */ } 158 }; 159 MODULE_DEVICE_TABLE(of, meson_wdt_dt_ids); 160 161 static int meson_wdt_probe(struct platform_device *pdev) 162 { 163 struct device *dev = &pdev->dev; 164 struct meson_wdt_dev *meson_wdt; 165 const struct of_device_id *of_id; 166 int err; 167 168 meson_wdt = devm_kzalloc(dev, sizeof(*meson_wdt), GFP_KERNEL); 169 if (!meson_wdt) 170 return -ENOMEM; 171 172 meson_wdt->wdt_base = devm_platform_ioremap_resource(pdev, 0); 173 if (IS_ERR(meson_wdt->wdt_base)) 174 return PTR_ERR(meson_wdt->wdt_base); 175 176 of_id = of_match_device(meson_wdt_dt_ids, dev); 177 if (!of_id) { 178 dev_err(dev, "Unable to initialize WDT data\n"); 179 return -ENODEV; 180 } 181 meson_wdt->data = of_id->data; 182 183 meson_wdt->wdt_dev.parent = dev; 184 meson_wdt->wdt_dev.info = &meson_wdt_info; 185 meson_wdt->wdt_dev.ops = &meson_wdt_ops; 186 meson_wdt->wdt_dev.max_timeout = 187 meson_wdt->data->terminal_count_mask / meson_wdt->data->count_unit; 188 meson_wdt->wdt_dev.min_timeout = MESON_WDT_MIN_TIMEOUT; 189 meson_wdt->wdt_dev.timeout = min_t(unsigned int, 190 MESON_WDT_TIMEOUT, 191 meson_wdt->wdt_dev.max_timeout); 192 193 watchdog_set_drvdata(&meson_wdt->wdt_dev, meson_wdt); 194 195 watchdog_init_timeout(&meson_wdt->wdt_dev, timeout, dev); 196 watchdog_set_nowayout(&meson_wdt->wdt_dev, nowayout); 197 watchdog_set_restart_priority(&meson_wdt->wdt_dev, 128); 198 199 meson_wdt_stop(&meson_wdt->wdt_dev); 200 201 watchdog_stop_on_reboot(&meson_wdt->wdt_dev); 202 err = devm_watchdog_register_device(dev, &meson_wdt->wdt_dev); 203 if (err) 204 return err; 205 206 dev_info(dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)", 207 meson_wdt->wdt_dev.timeout, nowayout); 208 209 return 0; 210 } 211 212 static struct platform_driver meson_wdt_driver = { 213 .probe = meson_wdt_probe, 214 .driver = { 215 .name = DRV_NAME, 216 .of_match_table = meson_wdt_dt_ids, 217 }, 218 }; 219 220 module_platform_driver(meson_wdt_driver); 221 222 module_param(timeout, uint, 0); 223 MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds"); 224 225 module_param(nowayout, bool, 0); 226 MODULE_PARM_DESC(nowayout, 227 "Watchdog cannot be stopped once started (default=" 228 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 229 230 MODULE_LICENSE("GPL"); 231 MODULE_AUTHOR("Carlo Caione <carlo@caione.org>"); 232 MODULE_DESCRIPTION("Meson Watchdog Timer Driver"); 233