Blame view

drivers/watchdog/pm8916_wdt.c 5.98 KB
969c0acc0   Loic Poulain   watchdog: Add pm8...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
  // SPDX-License-Identifier: GPL-2.0
  #include <linux/bitops.h>
  #include <linux/interrupt.h>
  #include <linux/kernel.h>
  #include <linux/module.h>
  #include <linux/of.h>
  #include <linux/property.h>
  #include <linux/platform_device.h>
  #include <linux/regmap.h>
  #include <linux/watchdog.h>
  
  #define PON_INT_RT_STS			0x10
  #define PMIC_WD_BARK_STS_BIT		BIT(6)
  
  #define PON_PMIC_WD_RESET_S1_TIMER	0x54
  #define PON_PMIC_WD_RESET_S2_TIMER	0x55
  
  #define PON_PMIC_WD_RESET_S2_CTL	0x56
  #define RESET_TYPE_WARM			0x01
  #define RESET_TYPE_SHUTDOWN		0x04
  #define RESET_TYPE_HARD			0x07
  
  #define PON_PMIC_WD_RESET_S2_CTL2	0x57
  #define S2_RESET_EN_BIT			BIT(7)
  
  #define PON_PMIC_WD_RESET_PET		0x58
  #define WATCHDOG_PET_BIT		BIT(0)
  
  #define PM8916_WDT_DEFAULT_TIMEOUT	32
  #define PM8916_WDT_MIN_TIMEOUT		1
  #define PM8916_WDT_MAX_TIMEOUT		127
  
  struct pm8916_wdt {
  	struct regmap *regmap;
  	struct watchdog_device wdev;
  	u32 baseaddr;
  };
  
  static int pm8916_wdt_start(struct watchdog_device *wdev)
  {
  	struct pm8916_wdt *wdt = watchdog_get_drvdata(wdev);
  
  	return regmap_update_bits(wdt->regmap,
  				  wdt->baseaddr + PON_PMIC_WD_RESET_S2_CTL2,
  				  S2_RESET_EN_BIT, S2_RESET_EN_BIT);
  }
  
  static int pm8916_wdt_stop(struct watchdog_device *wdev)
  {
  	struct pm8916_wdt *wdt = watchdog_get_drvdata(wdev);
  
  	return regmap_update_bits(wdt->regmap,
  				  wdt->baseaddr + PON_PMIC_WD_RESET_S2_CTL2,
  				  S2_RESET_EN_BIT, 0);
  }
  
  static int pm8916_wdt_ping(struct watchdog_device *wdev)
  {
  	struct pm8916_wdt *wdt = watchdog_get_drvdata(wdev);
  
  	return regmap_update_bits(wdt->regmap,
  				  wdt->baseaddr + PON_PMIC_WD_RESET_PET,
  				  WATCHDOG_PET_BIT, WATCHDOG_PET_BIT);
  }
  
  static int pm8916_wdt_configure_timers(struct watchdog_device *wdev)
  {
  	struct pm8916_wdt *wdt = watchdog_get_drvdata(wdev);
  	int err;
  
  	err = regmap_write(wdt->regmap,
  			   wdt->baseaddr + PON_PMIC_WD_RESET_S1_TIMER,
  			   wdev->timeout - wdev->pretimeout);
  	if (err)
  		return err;
  
  	return regmap_write(wdt->regmap,
  			    wdt->baseaddr + PON_PMIC_WD_RESET_S2_TIMER,
  			    wdev->pretimeout);
  }
  
  static int pm8916_wdt_set_timeout(struct watchdog_device *wdev,
  				  unsigned int timeout)
  {
  	wdev->timeout = timeout;
  
  	return pm8916_wdt_configure_timers(wdev);
  }
  
  static int pm8916_wdt_set_pretimeout(struct watchdog_device *wdev,
  				     unsigned int pretimeout)
  {
  	wdev->pretimeout = pretimeout;
  
  	return pm8916_wdt_configure_timers(wdev);
  }
  
  static irqreturn_t pm8916_wdt_isr(int irq, void *arg)
  {
  	struct pm8916_wdt *wdt = arg;
  	int err, sts;
  
  	err = regmap_read(wdt->regmap, wdt->baseaddr + PON_INT_RT_STS, &sts);
  	if (err)
  		return IRQ_HANDLED;
  
  	if (sts & PMIC_WD_BARK_STS_BIT)
  		watchdog_notify_pretimeout(&wdt->wdev);
  
  	return IRQ_HANDLED;
  }
  
  static const struct watchdog_info pm8916_wdt_ident = {
  	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
  	.identity = "QCOM PM8916 PON WDT",
  };
  
  static const struct watchdog_info pm8916_wdt_pt_ident = {
  	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE |
  		   WDIOF_PRETIMEOUT,
  	.identity = "QCOM PM8916 PON WDT",
  };
  
  static const struct watchdog_ops pm8916_wdt_ops = {
  	.owner = THIS_MODULE,
  	.start = pm8916_wdt_start,
  	.stop = pm8916_wdt_stop,
  	.ping = pm8916_wdt_ping,
  	.set_timeout = pm8916_wdt_set_timeout,
  	.set_pretimeout = pm8916_wdt_set_pretimeout,
  };
  
  static int pm8916_wdt_probe(struct platform_device *pdev)
  {
9723a82a9   Guenter Roeck   watchdog: pm8916_...
135
  	struct device *dev = &pdev->dev;
969c0acc0   Loic Poulain   watchdog: Add pm8...
136
137
138
  	struct pm8916_wdt *wdt;
  	struct device *parent;
  	int err, irq;
9723a82a9   Guenter Roeck   watchdog: pm8916_...
139
  	wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
969c0acc0   Loic Poulain   watchdog: Add pm8...
140
141
  	if (!wdt)
  		return -ENOMEM;
9723a82a9   Guenter Roeck   watchdog: pm8916_...
142
  	parent = dev->parent;
969c0acc0   Loic Poulain   watchdog: Add pm8...
143
144
145
146
147
148
149
150
151
  
  	/*
  	 * The pm8916-pon-wdt is a child of the pon device, which is a child
  	 * of the pm8916 mfd device. We want access to the pm8916 registers.
  	 * Retrieve regmap from pm8916 (parent->parent) and base address
  	 * from pm8916-pon (pon).
  	 */
  	wdt->regmap = dev_get_regmap(parent->parent, NULL);
  	if (!wdt->regmap) {
9723a82a9   Guenter Roeck   watchdog: pm8916_...
152
153
  		dev_err(dev, "failed to locate regmap
  ");
969c0acc0   Loic Poulain   watchdog: Add pm8...
154
155
156
157
158
  		return -ENODEV;
  	}
  
  	err = device_property_read_u32(parent, "reg", &wdt->baseaddr);
  	if (err) {
9723a82a9   Guenter Roeck   watchdog: pm8916_...
159
160
  		dev_err(dev, "failed to get pm8916-pon address
  ");
969c0acc0   Loic Poulain   watchdog: Add pm8...
161
162
163
164
165
  		return err;
  	}
  
  	irq = platform_get_irq(pdev, 0);
  	if (irq > 0) {
1993f1d7c   Jorge Ramirez-Ortiz   watchdog: pm8916_...
166
167
168
169
170
171
172
173
174
175
176
  		err = devm_request_irq(dev, irq, pm8916_wdt_isr, 0,
  				       "pm8916_wdt", wdt);
  		if (err)
  			return err;
  
  		wdt->wdev.info = &pm8916_wdt_pt_ident;
  	} else {
  		if (irq == -EPROBE_DEFER)
  			return -EPROBE_DEFER;
  
  		wdt->wdev.info = &pm8916_wdt_ident;
969c0acc0   Loic Poulain   watchdog: Add pm8...
177
178
179
180
181
182
183
  	}
  
  	/* Configure watchdog to hard-reset mode */
  	err = regmap_write(wdt->regmap,
  			   wdt->baseaddr + PON_PMIC_WD_RESET_S2_CTL,
  			   RESET_TYPE_HARD);
  	if (err) {
9723a82a9   Guenter Roeck   watchdog: pm8916_...
184
185
  		dev_err(dev, "failed configure watchdog
  ");
969c0acc0   Loic Poulain   watchdog: Add pm8...
186
187
  		return err;
  	}
969c0acc0   Loic Poulain   watchdog: Add pm8...
188
  	wdt->wdev.ops = &pm8916_wdt_ops,
9723a82a9   Guenter Roeck   watchdog: pm8916_...
189
  	wdt->wdev.parent = dev;
969c0acc0   Loic Poulain   watchdog: Add pm8...
190
191
192
193
194
  	wdt->wdev.min_timeout = PM8916_WDT_MIN_TIMEOUT;
  	wdt->wdev.max_timeout = PM8916_WDT_MAX_TIMEOUT;
  	wdt->wdev.timeout = PM8916_WDT_DEFAULT_TIMEOUT;
  	wdt->wdev.pretimeout = 0;
  	watchdog_set_drvdata(&wdt->wdev, wdt);
b1413e6ed   Loic Poulain   watchdog: pm8916_...
195
  	platform_set_drvdata(pdev, wdt);
969c0acc0   Loic Poulain   watchdog: Add pm8...
196

9723a82a9   Guenter Roeck   watchdog: pm8916_...
197
  	watchdog_init_timeout(&wdt->wdev, 0, dev);
969c0acc0   Loic Poulain   watchdog: Add pm8...
198
  	pm8916_wdt_configure_timers(&wdt->wdev);
9723a82a9   Guenter Roeck   watchdog: pm8916_...
199
  	return devm_watchdog_register_device(dev, &wdt->wdev);
969c0acc0   Loic Poulain   watchdog: Add pm8...
200
  }
b1413e6ed   Loic Poulain   watchdog: pm8916_...
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
  static int __maybe_unused pm8916_wdt_suspend(struct device *dev)
  {
  	struct pm8916_wdt *wdt = dev_get_drvdata(dev);
  
  	if (watchdog_active(&wdt->wdev))
  		return pm8916_wdt_stop(&wdt->wdev);
  
  	return 0;
  }
  
  static int __maybe_unused pm8916_wdt_resume(struct device *dev)
  {
  	struct pm8916_wdt *wdt = dev_get_drvdata(dev);
  
  	if (watchdog_active(&wdt->wdev))
  		return pm8916_wdt_start(&wdt->wdev);
  
  	return 0;
  }
  
  static SIMPLE_DEV_PM_OPS(pm8916_wdt_pm_ops, pm8916_wdt_suspend,
  			 pm8916_wdt_resume);
969c0acc0   Loic Poulain   watchdog: Add pm8...
223
224
225
226
227
228
229
230
231
232
233
  static const struct of_device_id pm8916_wdt_id_table[] = {
  	{ .compatible = "qcom,pm8916-wdt" },
  	{ }
  };
  MODULE_DEVICE_TABLE(of, pm8916_wdt_id_table);
  
  static struct platform_driver pm8916_wdt_driver = {
  	.probe = pm8916_wdt_probe,
  	.driver = {
  		.name = "pm8916-wdt",
  		.of_match_table = of_match_ptr(pm8916_wdt_id_table),
b1413e6ed   Loic Poulain   watchdog: pm8916_...
234
  		.pm = &pm8916_wdt_pm_ops,
969c0acc0   Loic Poulain   watchdog: Add pm8...
235
236
237
238
239
240
241
  	},
  };
  module_platform_driver(pm8916_wdt_driver);
  
  MODULE_AUTHOR("Loic Poulain <loic.poulain@linaro.org>");
  MODULE_DESCRIPTION("Qualcomm pm8916 watchdog driver");
  MODULE_LICENSE("GPL v2");