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