1// SPDX-License-Identifier: GPL-2.0 2#include <linux/bitops.h> 3#include <linux/interrupt.h> 4#include <linux/kernel.h> 5#include <linux/module.h> 6#include <linux/of.h> 7#include <linux/property.h> 8#include <linux/platform_device.h> 9#include <linux/regmap.h> 10#include <linux/watchdog.h> 11 12#define PON_POFF_REASON1 0x0c 13#define PON_POFF_REASON1_PMIC_WD BIT(2) 14#define PON_POFF_REASON2 0x0d 15#define PON_POFF_REASON2_UVLO BIT(5) 16#define PON_POFF_REASON2_OTST3 BIT(6) 17 18#define PON_INT_RT_STS 0x10 19#define PMIC_WD_BARK_STS_BIT BIT(6) 20 21#define PON_PMIC_WD_RESET_S1_TIMER 0x54 22#define PON_PMIC_WD_RESET_S2_TIMER 0x55 23 24#define PON_PMIC_WD_RESET_S2_CTL 0x56 25#define RESET_TYPE_WARM 0x01 26#define RESET_TYPE_SHUTDOWN 0x04 27#define RESET_TYPE_HARD 0x07 28 29#define PON_PMIC_WD_RESET_S2_CTL2 0x57 30#define S2_RESET_EN_BIT BIT(7) 31 32#define PON_PMIC_WD_RESET_PET 0x58 33#define WATCHDOG_PET_BIT BIT(0) 34 35#define PM8916_WDT_DEFAULT_TIMEOUT 32 36#define PM8916_WDT_MIN_TIMEOUT 1 37#define PM8916_WDT_MAX_TIMEOUT 127 38 39struct pm8916_wdt { 40 struct regmap *regmap; 41 struct watchdog_device wdev; 42 u32 baseaddr; 43}; 44 45static int pm8916_wdt_start(struct watchdog_device *wdev) 46{ 47 struct pm8916_wdt *wdt = watchdog_get_drvdata(wdev); 48 49 return regmap_update_bits(wdt->regmap, 50 wdt->baseaddr + PON_PMIC_WD_RESET_S2_CTL2, 51 S2_RESET_EN_BIT, S2_RESET_EN_BIT); 52} 53 54static int pm8916_wdt_stop(struct watchdog_device *wdev) 55{ 56 struct pm8916_wdt *wdt = watchdog_get_drvdata(wdev); 57 58 return regmap_update_bits(wdt->regmap, 59 wdt->baseaddr + PON_PMIC_WD_RESET_S2_CTL2, 60 S2_RESET_EN_BIT, 0); 61} 62 63static int pm8916_wdt_ping(struct watchdog_device *wdev) 64{ 65 struct pm8916_wdt *wdt = watchdog_get_drvdata(wdev); 66 67 return regmap_write(wdt->regmap, wdt->baseaddr + PON_PMIC_WD_RESET_PET, 68 WATCHDOG_PET_BIT); 69} 70 71static int pm8916_wdt_configure_timers(struct watchdog_device *wdev) 72{ 73 struct pm8916_wdt *wdt = watchdog_get_drvdata(wdev); 74 int err; 75 76 err = regmap_write(wdt->regmap, 77 wdt->baseaddr + PON_PMIC_WD_RESET_S1_TIMER, 78 wdev->timeout - wdev->pretimeout); 79 if (err) 80 return err; 81 82 return regmap_write(wdt->regmap, 83 wdt->baseaddr + PON_PMIC_WD_RESET_S2_TIMER, 84 wdev->pretimeout); 85} 86 87static int pm8916_wdt_set_timeout(struct watchdog_device *wdev, 88 unsigned int timeout) 89{ 90 wdev->timeout = timeout; 91 92 return pm8916_wdt_configure_timers(wdev); 93} 94 95static int pm8916_wdt_set_pretimeout(struct watchdog_device *wdev, 96 unsigned int pretimeout) 97{ 98 wdev->pretimeout = pretimeout; 99 100 return pm8916_wdt_configure_timers(wdev); 101} 102 103static irqreturn_t pm8916_wdt_isr(int irq, void *arg) 104{ 105 struct pm8916_wdt *wdt = arg; 106 int err, sts; 107 108 err = regmap_read(wdt->regmap, wdt->baseaddr + PON_INT_RT_STS, &sts); 109 if (err) 110 return IRQ_HANDLED; 111 112 if (sts & PMIC_WD_BARK_STS_BIT) 113 watchdog_notify_pretimeout(&wdt->wdev); 114 115 return IRQ_HANDLED; 116} 117 118static const struct watchdog_info pm8916_wdt_ident = { 119 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | 120 WDIOF_OVERHEAT | WDIOF_CARDRESET | WDIOF_POWERUNDER, 121 .identity = "QCOM PM8916 PON WDT", 122}; 123 124static const struct watchdog_info pm8916_wdt_pt_ident = { 125 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | 126 WDIOF_OVERHEAT | WDIOF_CARDRESET | WDIOF_POWERUNDER | 127 WDIOF_PRETIMEOUT, 128 .identity = "QCOM PM8916 PON WDT", 129}; 130 131static const struct watchdog_ops pm8916_wdt_ops = { 132 .owner = THIS_MODULE, 133 .start = pm8916_wdt_start, 134 .stop = pm8916_wdt_stop, 135 .ping = pm8916_wdt_ping, 136 .set_timeout = pm8916_wdt_set_timeout, 137 .set_pretimeout = pm8916_wdt_set_pretimeout, 138}; 139 140static int pm8916_wdt_probe(struct platform_device *pdev) 141{ 142 struct device *dev = &pdev->dev; 143 struct pm8916_wdt *wdt; 144 struct device *parent; 145 unsigned int val; 146 int err, irq; 147 u8 poff[2]; 148 149 wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); 150 if (!wdt) 151 return -ENOMEM; 152 153 parent = dev->parent; 154 155 /* 156 * The pm8916-pon-wdt is a child of the pon device, which is a child 157 * of the pm8916 mfd device. We want access to the pm8916 registers. 158 * Retrieve regmap from pm8916 (parent->parent) and base address 159 * from pm8916-pon (pon). 160 */ 161 wdt->regmap = dev_get_regmap(parent->parent, NULL); 162 if (!wdt->regmap) { 163 dev_err(dev, "failed to locate regmap\n"); 164 return -ENODEV; 165 } 166 167 err = device_property_read_u32(parent, "reg", &wdt->baseaddr); 168 if (err) { 169 dev_err(dev, "failed to get pm8916-pon address\n"); 170 return err; 171 } 172 173 irq = platform_get_irq(pdev, 0); 174 if (irq > 0) { 175 err = devm_request_irq(dev, irq, pm8916_wdt_isr, 0, 176 "pm8916_wdt", wdt); 177 if (err) 178 return err; 179 180 wdt->wdev.info = &pm8916_wdt_pt_ident; 181 } else { 182 if (irq == -EPROBE_DEFER) 183 return -EPROBE_DEFER; 184 185 wdt->wdev.info = &pm8916_wdt_ident; 186 } 187 188 err = regmap_bulk_read(wdt->regmap, wdt->baseaddr + PON_POFF_REASON1, 189 &poff, ARRAY_SIZE(poff)); 190 if (err) { 191 dev_err(dev, "failed to read POFF reason: %d\n", err); 192 return err; 193 } 194 195 dev_dbg(dev, "POFF reason: %#x %#x\n", poff[0], poff[1]); 196 if (poff[0] & PON_POFF_REASON1_PMIC_WD) 197 wdt->wdev.bootstatus |= WDIOF_CARDRESET; 198 if (poff[1] & PON_POFF_REASON2_UVLO) 199 wdt->wdev.bootstatus |= WDIOF_POWERUNDER; 200 if (poff[1] & PON_POFF_REASON2_OTST3) 201 wdt->wdev.bootstatus |= WDIOF_OVERHEAT; 202 203 err = regmap_read(wdt->regmap, wdt->baseaddr + PON_PMIC_WD_RESET_S2_CTL2, 204 &val); 205 if (err) { 206 dev_err(dev, "failed to check if watchdog is active: %d\n", err); 207 return err; 208 } 209 if (val & S2_RESET_EN_BIT) 210 set_bit(WDOG_HW_RUNNING, &wdt->wdev.status); 211 212 /* Configure watchdog to hard-reset mode */ 213 err = regmap_write(wdt->regmap, 214 wdt->baseaddr + PON_PMIC_WD_RESET_S2_CTL, 215 RESET_TYPE_HARD); 216 if (err) { 217 dev_err(dev, "failed configure watchdog\n"); 218 return err; 219 } 220 221 wdt->wdev.ops = &pm8916_wdt_ops, 222 wdt->wdev.parent = dev; 223 wdt->wdev.min_timeout = PM8916_WDT_MIN_TIMEOUT; 224 wdt->wdev.max_timeout = PM8916_WDT_MAX_TIMEOUT; 225 wdt->wdev.timeout = PM8916_WDT_DEFAULT_TIMEOUT; 226 wdt->wdev.pretimeout = 0; 227 watchdog_set_drvdata(&wdt->wdev, wdt); 228 platform_set_drvdata(pdev, wdt); 229 230 watchdog_init_timeout(&wdt->wdev, 0, dev); 231 pm8916_wdt_configure_timers(&wdt->wdev); 232 233 return devm_watchdog_register_device(dev, &wdt->wdev); 234} 235 236static int __maybe_unused pm8916_wdt_suspend(struct device *dev) 237{ 238 struct pm8916_wdt *wdt = dev_get_drvdata(dev); 239 240 if (watchdog_active(&wdt->wdev)) 241 return pm8916_wdt_stop(&wdt->wdev); 242 243 return 0; 244} 245 246static int __maybe_unused pm8916_wdt_resume(struct device *dev) 247{ 248 struct pm8916_wdt *wdt = dev_get_drvdata(dev); 249 250 if (watchdog_active(&wdt->wdev)) 251 return pm8916_wdt_start(&wdt->wdev); 252 253 return 0; 254} 255 256static SIMPLE_DEV_PM_OPS(pm8916_wdt_pm_ops, pm8916_wdt_suspend, 257 pm8916_wdt_resume); 258 259static const struct of_device_id pm8916_wdt_id_table[] = { 260 { .compatible = "qcom,pm8916-wdt" }, 261 { } 262}; 263MODULE_DEVICE_TABLE(of, pm8916_wdt_id_table); 264 265static struct platform_driver pm8916_wdt_driver = { 266 .probe = pm8916_wdt_probe, 267 .driver = { 268 .name = "pm8916-wdt", 269 .of_match_table = pm8916_wdt_id_table, 270 .pm = &pm8916_wdt_pm_ops, 271 }, 272}; 273module_platform_driver(pm8916_wdt_driver); 274 275MODULE_AUTHOR("Loic Poulain <loic.poulain@linaro.org>"); 276MODULE_DESCRIPTION("Qualcomm pm8916 watchdog driver"); 277MODULE_LICENSE("GPL v2"); 278