278 lines · c
1// SPDX-License-Identifier: GPL-2.02#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 0x0c13#define PON_POFF_REASON1_PMIC_WD BIT(2)14#define PON_POFF_REASON2 0x0d15#define PON_POFF_REASON2_UVLO BIT(5)16#define PON_POFF_REASON2_OTST3 BIT(6)17 18#define PON_INT_RT_STS 0x1019#define PMIC_WD_BARK_STS_BIT BIT(6)20 21#define PON_PMIC_WD_RESET_S1_TIMER 0x5422#define PON_PMIC_WD_RESET_S2_TIMER 0x5523 24#define PON_PMIC_WD_RESET_S2_CTL 0x5625#define RESET_TYPE_WARM 0x0126#define RESET_TYPE_SHUTDOWN 0x0427#define RESET_TYPE_HARD 0x0728 29#define PON_PMIC_WD_RESET_S2_CTL2 0x5730#define S2_RESET_EN_BIT BIT(7)31 32#define PON_PMIC_WD_RESET_PET 0x5833#define WATCHDOG_PET_BIT BIT(0)34 35#define PM8916_WDT_DEFAULT_TIMEOUT 3236#define PM8916_WDT_MIN_TIMEOUT 137#define PM8916_WDT_MAX_TIMEOUT 12738 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 child157 * of the pm8916 mfd device. We want access to the pm8916 registers.158 * Retrieve regmap from pm8916 (parent->parent) and base address159 * 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