Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 #include <linux/bitops.h>
0003 #include <linux/interrupt.h>
0004 #include <linux/kernel.h>
0005 #include <linux/module.h>
0006 #include <linux/of.h>
0007 #include <linux/property.h>
0008 #include <linux/platform_device.h>
0009 #include <linux/regmap.h>
0010 #include <linux/watchdog.h>
0011 
0012 #define PON_POFF_REASON1        0x0c
0013 #define PON_POFF_REASON1_PMIC_WD    BIT(2)
0014 #define PON_POFF_REASON2        0x0d
0015 #define PON_POFF_REASON2_UVLO       BIT(5)
0016 #define PON_POFF_REASON2_OTST3      BIT(6)
0017 
0018 #define PON_INT_RT_STS          0x10
0019 #define PMIC_WD_BARK_STS_BIT        BIT(6)
0020 
0021 #define PON_PMIC_WD_RESET_S1_TIMER  0x54
0022 #define PON_PMIC_WD_RESET_S2_TIMER  0x55
0023 
0024 #define PON_PMIC_WD_RESET_S2_CTL    0x56
0025 #define RESET_TYPE_WARM         0x01
0026 #define RESET_TYPE_SHUTDOWN     0x04
0027 #define RESET_TYPE_HARD         0x07
0028 
0029 #define PON_PMIC_WD_RESET_S2_CTL2   0x57
0030 #define S2_RESET_EN_BIT         BIT(7)
0031 
0032 #define PON_PMIC_WD_RESET_PET       0x58
0033 #define WATCHDOG_PET_BIT        BIT(0)
0034 
0035 #define PM8916_WDT_DEFAULT_TIMEOUT  32
0036 #define PM8916_WDT_MIN_TIMEOUT      1
0037 #define PM8916_WDT_MAX_TIMEOUT      127
0038 
0039 struct pm8916_wdt {
0040     struct regmap *regmap;
0041     struct watchdog_device wdev;
0042     u32 baseaddr;
0043 };
0044 
0045 static int pm8916_wdt_start(struct watchdog_device *wdev)
0046 {
0047     struct pm8916_wdt *wdt = watchdog_get_drvdata(wdev);
0048 
0049     return regmap_update_bits(wdt->regmap,
0050                   wdt->baseaddr + PON_PMIC_WD_RESET_S2_CTL2,
0051                   S2_RESET_EN_BIT, S2_RESET_EN_BIT);
0052 }
0053 
0054 static int pm8916_wdt_stop(struct watchdog_device *wdev)
0055 {
0056     struct pm8916_wdt *wdt = watchdog_get_drvdata(wdev);
0057 
0058     return regmap_update_bits(wdt->regmap,
0059                   wdt->baseaddr + PON_PMIC_WD_RESET_S2_CTL2,
0060                   S2_RESET_EN_BIT, 0);
0061 }
0062 
0063 static int pm8916_wdt_ping(struct watchdog_device *wdev)
0064 {
0065     struct pm8916_wdt *wdt = watchdog_get_drvdata(wdev);
0066 
0067     return regmap_write(wdt->regmap, wdt->baseaddr + PON_PMIC_WD_RESET_PET,
0068                 WATCHDOG_PET_BIT);
0069 }
0070 
0071 static int pm8916_wdt_configure_timers(struct watchdog_device *wdev)
0072 {
0073     struct pm8916_wdt *wdt = watchdog_get_drvdata(wdev);
0074     int err;
0075 
0076     err = regmap_write(wdt->regmap,
0077                wdt->baseaddr + PON_PMIC_WD_RESET_S1_TIMER,
0078                wdev->timeout - wdev->pretimeout);
0079     if (err)
0080         return err;
0081 
0082     return regmap_write(wdt->regmap,
0083                 wdt->baseaddr + PON_PMIC_WD_RESET_S2_TIMER,
0084                 wdev->pretimeout);
0085 }
0086 
0087 static int pm8916_wdt_set_timeout(struct watchdog_device *wdev,
0088                   unsigned int timeout)
0089 {
0090     wdev->timeout = timeout;
0091 
0092     return pm8916_wdt_configure_timers(wdev);
0093 }
0094 
0095 static int pm8916_wdt_set_pretimeout(struct watchdog_device *wdev,
0096                      unsigned int pretimeout)
0097 {
0098     wdev->pretimeout = pretimeout;
0099 
0100     return pm8916_wdt_configure_timers(wdev);
0101 }
0102 
0103 static irqreturn_t pm8916_wdt_isr(int irq, void *arg)
0104 {
0105     struct pm8916_wdt *wdt = arg;
0106     int err, sts;
0107 
0108     err = regmap_read(wdt->regmap, wdt->baseaddr + PON_INT_RT_STS, &sts);
0109     if (err)
0110         return IRQ_HANDLED;
0111 
0112     if (sts & PMIC_WD_BARK_STS_BIT)
0113         watchdog_notify_pretimeout(&wdt->wdev);
0114 
0115     return IRQ_HANDLED;
0116 }
0117 
0118 static const struct watchdog_info pm8916_wdt_ident = {
0119     .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE |
0120            WDIOF_OVERHEAT | WDIOF_CARDRESET | WDIOF_POWERUNDER,
0121     .identity = "QCOM PM8916 PON WDT",
0122 };
0123 
0124 static const struct watchdog_info pm8916_wdt_pt_ident = {
0125     .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE |
0126            WDIOF_OVERHEAT | WDIOF_CARDRESET | WDIOF_POWERUNDER |
0127            WDIOF_PRETIMEOUT,
0128     .identity = "QCOM PM8916 PON WDT",
0129 };
0130 
0131 static const struct watchdog_ops pm8916_wdt_ops = {
0132     .owner = THIS_MODULE,
0133     .start = pm8916_wdt_start,
0134     .stop = pm8916_wdt_stop,
0135     .ping = pm8916_wdt_ping,
0136     .set_timeout = pm8916_wdt_set_timeout,
0137     .set_pretimeout = pm8916_wdt_set_pretimeout,
0138 };
0139 
0140 static int pm8916_wdt_probe(struct platform_device *pdev)
0141 {
0142     struct device *dev = &pdev->dev;
0143     struct pm8916_wdt *wdt;
0144     struct device *parent;
0145     unsigned int val;
0146     int err, irq;
0147     u8 poff[2];
0148 
0149     wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
0150     if (!wdt)
0151         return -ENOMEM;
0152 
0153     parent = dev->parent;
0154 
0155     /*
0156      * The pm8916-pon-wdt is a child of the pon device, which is a child
0157      * of the pm8916 mfd device. We want access to the pm8916 registers.
0158      * Retrieve regmap from pm8916 (parent->parent) and base address
0159      * from pm8916-pon (pon).
0160      */
0161     wdt->regmap = dev_get_regmap(parent->parent, NULL);
0162     if (!wdt->regmap) {
0163         dev_err(dev, "failed to locate regmap\n");
0164         return -ENODEV;
0165     }
0166 
0167     err = device_property_read_u32(parent, "reg", &wdt->baseaddr);
0168     if (err) {
0169         dev_err(dev, "failed to get pm8916-pon address\n");
0170         return err;
0171     }
0172 
0173     irq = platform_get_irq(pdev, 0);
0174     if (irq > 0) {
0175         err = devm_request_irq(dev, irq, pm8916_wdt_isr, 0,
0176                        "pm8916_wdt", wdt);
0177         if (err)
0178             return err;
0179 
0180         wdt->wdev.info = &pm8916_wdt_pt_ident;
0181     } else {
0182         if (irq == -EPROBE_DEFER)
0183             return -EPROBE_DEFER;
0184 
0185         wdt->wdev.info = &pm8916_wdt_ident;
0186     }
0187 
0188     err = regmap_bulk_read(wdt->regmap, wdt->baseaddr + PON_POFF_REASON1,
0189                    &poff, ARRAY_SIZE(poff));
0190     if (err) {
0191         dev_err(dev, "failed to read POFF reason: %d\n", err);
0192         return err;
0193     }
0194 
0195     dev_dbg(dev, "POFF reason: %#x %#x\n", poff[0], poff[1]);
0196     if (poff[0] & PON_POFF_REASON1_PMIC_WD)
0197         wdt->wdev.bootstatus |= WDIOF_CARDRESET;
0198     if (poff[1] & PON_POFF_REASON2_UVLO)
0199         wdt->wdev.bootstatus |= WDIOF_POWERUNDER;
0200     if (poff[1] & PON_POFF_REASON2_OTST3)
0201         wdt->wdev.bootstatus |= WDIOF_OVERHEAT;
0202 
0203     err = regmap_read(wdt->regmap, wdt->baseaddr + PON_PMIC_WD_RESET_S2_CTL2,
0204               &val);
0205     if (err)  {
0206         dev_err(dev, "failed to check if watchdog is active: %d\n", err);
0207         return err;
0208     }
0209     if (val & S2_RESET_EN_BIT)
0210         set_bit(WDOG_HW_RUNNING, &wdt->wdev.status);
0211 
0212     /* Configure watchdog to hard-reset mode */
0213     err = regmap_write(wdt->regmap,
0214                wdt->baseaddr + PON_PMIC_WD_RESET_S2_CTL,
0215                RESET_TYPE_HARD);
0216     if (err) {
0217         dev_err(dev, "failed configure watchdog\n");
0218         return err;
0219     }
0220 
0221     wdt->wdev.ops = &pm8916_wdt_ops,
0222     wdt->wdev.parent = dev;
0223     wdt->wdev.min_timeout = PM8916_WDT_MIN_TIMEOUT;
0224     wdt->wdev.max_timeout = PM8916_WDT_MAX_TIMEOUT;
0225     wdt->wdev.timeout = PM8916_WDT_DEFAULT_TIMEOUT;
0226     wdt->wdev.pretimeout = 0;
0227     watchdog_set_drvdata(&wdt->wdev, wdt);
0228     platform_set_drvdata(pdev, wdt);
0229 
0230     watchdog_init_timeout(&wdt->wdev, 0, dev);
0231     pm8916_wdt_configure_timers(&wdt->wdev);
0232 
0233     return devm_watchdog_register_device(dev, &wdt->wdev);
0234 }
0235 
0236 static int __maybe_unused pm8916_wdt_suspend(struct device *dev)
0237 {
0238     struct pm8916_wdt *wdt = dev_get_drvdata(dev);
0239 
0240     if (watchdog_active(&wdt->wdev))
0241         return pm8916_wdt_stop(&wdt->wdev);
0242 
0243     return 0;
0244 }
0245 
0246 static int __maybe_unused pm8916_wdt_resume(struct device *dev)
0247 {
0248     struct pm8916_wdt *wdt = dev_get_drvdata(dev);
0249 
0250     if (watchdog_active(&wdt->wdev))
0251         return pm8916_wdt_start(&wdt->wdev);
0252 
0253     return 0;
0254 }
0255 
0256 static SIMPLE_DEV_PM_OPS(pm8916_wdt_pm_ops, pm8916_wdt_suspend,
0257              pm8916_wdt_resume);
0258 
0259 static const struct of_device_id pm8916_wdt_id_table[] = {
0260     { .compatible = "qcom,pm8916-wdt" },
0261     { }
0262 };
0263 MODULE_DEVICE_TABLE(of, pm8916_wdt_id_table);
0264 
0265 static struct platform_driver pm8916_wdt_driver = {
0266     .probe = pm8916_wdt_probe,
0267     .driver = {
0268         .name = "pm8916-wdt",
0269         .of_match_table = of_match_ptr(pm8916_wdt_id_table),
0270         .pm = &pm8916_wdt_pm_ops,
0271     },
0272 };
0273 module_platform_driver(pm8916_wdt_driver);
0274 
0275 MODULE_AUTHOR("Loic Poulain <loic.poulain@linaro.org>");
0276 MODULE_DESCRIPTION("Qualcomm pm8916 watchdog driver");
0277 MODULE_LICENSE("GPL v2");