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 39 struct pm8916_wdt { 40 struct regmap *regmap; 41 struct watchdog_device wdev; 42 u32 baseaddr; 43 }; 44 45 static 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 54 static 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 63 static 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 71 static 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 87 static 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 95 static 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 103 static 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 118 static 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 124 static 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 131 static 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 140 static 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 236 static 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 246 static 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 256 static SIMPLE_DEV_PM_OPS(pm8916_wdt_pm_ops, pm8916_wdt_suspend, 257 pm8916_wdt_resume); 258 259 static const struct of_device_id pm8916_wdt_id_table[] = { 260 { .compatible = "qcom,pm8916-wdt" }, 261 { } 262 }; 263 MODULE_DEVICE_TABLE(of, pm8916_wdt_id_table); 264 265 static struct platform_driver pm8916_wdt_driver = { 266 .probe = pm8916_wdt_probe, 267 .driver = { 268 .name = "pm8916-wdt", 269 .of_match_table = of_match_ptr(pm8916_wdt_id_table), 270 .pm = &pm8916_wdt_pm_ops, 271 }, 272 }; 273 module_platform_driver(pm8916_wdt_driver); 274 275 MODULE_AUTHOR("Loic Poulain <loic.poulain@linaro.org>"); 276 MODULE_DESCRIPTION("Qualcomm pm8916 watchdog driver"); 277 MODULE_LICENSE("GPL v2"); 278