xref: /openbmc/linux/drivers/watchdog/da9055_wdt.c (revision 312b00e1c31011dd009f51a52e14a500f61f1f31)
1*312b00e1SAshish Jangam /*
2*312b00e1SAshish Jangam  * System monitoring driver for DA9055 PMICs.
3*312b00e1SAshish Jangam  *
4*312b00e1SAshish Jangam  * Copyright(c) 2012 Dialog Semiconductor Ltd.
5*312b00e1SAshish Jangam  *
6*312b00e1SAshish Jangam  * Author: David Dajun Chen <dchen@diasemi.com>
7*312b00e1SAshish Jangam  *
8*312b00e1SAshish Jangam  * This program is free software; you can redistribute it and/or modify
9*312b00e1SAshish Jangam  * it under the terms of the GNU General Public License as published by
10*312b00e1SAshish Jangam  * the Free Software Foundation; either version 2 of the License, or
11*312b00e1SAshish Jangam  * (at your option) any later version.
12*312b00e1SAshish Jangam  *
13*312b00e1SAshish Jangam  */
14*312b00e1SAshish Jangam 
15*312b00e1SAshish Jangam #include <linux/module.h>
16*312b00e1SAshish Jangam #include <linux/types.h>
17*312b00e1SAshish Jangam #include <linux/kernel.h>
18*312b00e1SAshish Jangam #include <linux/slab.h>
19*312b00e1SAshish Jangam #include <linux/platform_device.h>
20*312b00e1SAshish Jangam #include <linux/watchdog.h>
21*312b00e1SAshish Jangam #include <linux/delay.h>
22*312b00e1SAshish Jangam 
23*312b00e1SAshish Jangam #include <linux/mfd/da9055/core.h>
24*312b00e1SAshish Jangam #include <linux/mfd/da9055/reg.h>
25*312b00e1SAshish Jangam 
26*312b00e1SAshish Jangam static bool nowayout = WATCHDOG_NOWAYOUT;
27*312b00e1SAshish Jangam module_param(nowayout, bool, 0);
28*312b00e1SAshish Jangam MODULE_PARM_DESC(nowayout,
29*312b00e1SAshish Jangam 		 "Watchdog cannot be stopped once started (default="
30*312b00e1SAshish Jangam 		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
31*312b00e1SAshish Jangam 
32*312b00e1SAshish Jangam #define DA9055_DEF_TIMEOUT	4
33*312b00e1SAshish Jangam #define DA9055_TWDMIN		256
34*312b00e1SAshish Jangam 
35*312b00e1SAshish Jangam struct da9055_wdt_data {
36*312b00e1SAshish Jangam 	struct watchdog_device wdt;
37*312b00e1SAshish Jangam 	struct da9055 *da9055;
38*312b00e1SAshish Jangam 	struct kref kref;
39*312b00e1SAshish Jangam };
40*312b00e1SAshish Jangam 
41*312b00e1SAshish Jangam static const struct {
42*312b00e1SAshish Jangam 	u8 reg_val;
43*312b00e1SAshish Jangam 	int user_time;  /* In seconds */
44*312b00e1SAshish Jangam } da9055_wdt_maps[] = {
45*312b00e1SAshish Jangam 	{ 0, 0 },
46*312b00e1SAshish Jangam 	{ 1, 2 },
47*312b00e1SAshish Jangam 	{ 2, 4 },
48*312b00e1SAshish Jangam 	{ 3, 8 },
49*312b00e1SAshish Jangam 	{ 4, 16 },
50*312b00e1SAshish Jangam 	{ 5, 32 },
51*312b00e1SAshish Jangam 	{ 5, 33 },  /* Actual time  32.768s so included both 32s and 33s */
52*312b00e1SAshish Jangam 	{ 6, 65 },
53*312b00e1SAshish Jangam 	{ 6, 66 },  /* Actual time 65.536s so include both, 65s and 66s */
54*312b00e1SAshish Jangam 	{ 7, 131 },
55*312b00e1SAshish Jangam };
56*312b00e1SAshish Jangam 
57*312b00e1SAshish Jangam static int da9055_wdt_set_timeout(struct watchdog_device *wdt_dev,
58*312b00e1SAshish Jangam 				  unsigned int timeout)
59*312b00e1SAshish Jangam {
60*312b00e1SAshish Jangam 	struct da9055_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev);
61*312b00e1SAshish Jangam 	struct da9055 *da9055 = driver_data->da9055;
62*312b00e1SAshish Jangam 	int ret, i;
63*312b00e1SAshish Jangam 
64*312b00e1SAshish Jangam 	for (i = 0; i < ARRAY_SIZE(da9055_wdt_maps); i++)
65*312b00e1SAshish Jangam 		if (da9055_wdt_maps[i].user_time == timeout)
66*312b00e1SAshish Jangam 			break;
67*312b00e1SAshish Jangam 
68*312b00e1SAshish Jangam 	if (i == ARRAY_SIZE(da9055_wdt_maps))
69*312b00e1SAshish Jangam 		ret = -EINVAL;
70*312b00e1SAshish Jangam 	else
71*312b00e1SAshish Jangam 		ret = da9055_reg_update(da9055, DA9055_REG_CONTROL_B,
72*312b00e1SAshish Jangam 					DA9055_TWDSCALE_MASK,
73*312b00e1SAshish Jangam 					da9055_wdt_maps[i].reg_val <<
74*312b00e1SAshish Jangam 					DA9055_TWDSCALE_SHIFT);
75*312b00e1SAshish Jangam 	if (ret < 0)
76*312b00e1SAshish Jangam 		dev_err(da9055->dev,
77*312b00e1SAshish Jangam 			"Failed to update timescale bit, %d\n", ret);
78*312b00e1SAshish Jangam 
79*312b00e1SAshish Jangam 	wdt_dev->timeout = timeout;
80*312b00e1SAshish Jangam 
81*312b00e1SAshish Jangam 	return ret;
82*312b00e1SAshish Jangam }
83*312b00e1SAshish Jangam 
84*312b00e1SAshish Jangam static int da9055_wdt_ping(struct watchdog_device *wdt_dev)
85*312b00e1SAshish Jangam {
86*312b00e1SAshish Jangam 	struct da9055_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev);
87*312b00e1SAshish Jangam 	struct da9055 *da9055 = driver_data->da9055;
88*312b00e1SAshish Jangam 	int ret;
89*312b00e1SAshish Jangam 
90*312b00e1SAshish Jangam 	/*
91*312b00e1SAshish Jangam 	 * We have a minimum time for watchdog window called TWDMIN. A write
92*312b00e1SAshish Jangam 	 * to the watchdog before this elapsed time will cause an error.
93*312b00e1SAshish Jangam 	 */
94*312b00e1SAshish Jangam 	mdelay(DA9055_TWDMIN);
95*312b00e1SAshish Jangam 
96*312b00e1SAshish Jangam 	/* Reset the watchdog timer */
97*312b00e1SAshish Jangam 	ret = da9055_reg_update(da9055, DA9055_REG_CONTROL_E,
98*312b00e1SAshish Jangam 				DA9055_WATCHDOG_MASK, 1);
99*312b00e1SAshish Jangam 
100*312b00e1SAshish Jangam 	return ret;
101*312b00e1SAshish Jangam }
102*312b00e1SAshish Jangam 
103*312b00e1SAshish Jangam static void da9055_wdt_release_resources(struct kref *r)
104*312b00e1SAshish Jangam {
105*312b00e1SAshish Jangam 	struct da9055_wdt_data *driver_data =
106*312b00e1SAshish Jangam 		container_of(r, struct da9055_wdt_data, kref);
107*312b00e1SAshish Jangam 
108*312b00e1SAshish Jangam 	kfree(driver_data);
109*312b00e1SAshish Jangam }
110*312b00e1SAshish Jangam 
111*312b00e1SAshish Jangam static void da9055_wdt_ref(struct watchdog_device *wdt_dev)
112*312b00e1SAshish Jangam {
113*312b00e1SAshish Jangam 	struct da9055_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev);
114*312b00e1SAshish Jangam 
115*312b00e1SAshish Jangam 	kref_get(&driver_data->kref);
116*312b00e1SAshish Jangam }
117*312b00e1SAshish Jangam 
118*312b00e1SAshish Jangam static void da9055_wdt_unref(struct watchdog_device *wdt_dev)
119*312b00e1SAshish Jangam {
120*312b00e1SAshish Jangam 	struct da9055_wdt_data *driver_data = watchdog_get_drvdata(wdt_dev);
121*312b00e1SAshish Jangam 
122*312b00e1SAshish Jangam 	kref_put(&driver_data->kref, da9055_wdt_release_resources);
123*312b00e1SAshish Jangam }
124*312b00e1SAshish Jangam 
125*312b00e1SAshish Jangam static int da9055_wdt_start(struct watchdog_device *wdt_dev)
126*312b00e1SAshish Jangam {
127*312b00e1SAshish Jangam 	return da9055_wdt_set_timeout(wdt_dev, wdt_dev->timeout);
128*312b00e1SAshish Jangam }
129*312b00e1SAshish Jangam 
130*312b00e1SAshish Jangam static int da9055_wdt_stop(struct watchdog_device *wdt_dev)
131*312b00e1SAshish Jangam {
132*312b00e1SAshish Jangam 	return da9055_wdt_set_timeout(wdt_dev, 0);
133*312b00e1SAshish Jangam }
134*312b00e1SAshish Jangam 
135*312b00e1SAshish Jangam static struct watchdog_info da9055_wdt_info = {
136*312b00e1SAshish Jangam 	.options	= WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
137*312b00e1SAshish Jangam 	.identity	= "DA9055 Watchdog",
138*312b00e1SAshish Jangam };
139*312b00e1SAshish Jangam 
140*312b00e1SAshish Jangam static const struct watchdog_ops da9055_wdt_ops = {
141*312b00e1SAshish Jangam 	.owner = THIS_MODULE,
142*312b00e1SAshish Jangam 	.start = da9055_wdt_start,
143*312b00e1SAshish Jangam 	.stop = da9055_wdt_stop,
144*312b00e1SAshish Jangam 	.ping = da9055_wdt_ping,
145*312b00e1SAshish Jangam 	.set_timeout = da9055_wdt_set_timeout,
146*312b00e1SAshish Jangam 	.ref = da9055_wdt_ref,
147*312b00e1SAshish Jangam 	.unref = da9055_wdt_unref,
148*312b00e1SAshish Jangam };
149*312b00e1SAshish Jangam 
150*312b00e1SAshish Jangam static int da9055_wdt_probe(struct platform_device *pdev)
151*312b00e1SAshish Jangam {
152*312b00e1SAshish Jangam 	struct da9055 *da9055 = dev_get_drvdata(pdev->dev.parent);
153*312b00e1SAshish Jangam 	struct da9055_wdt_data *driver_data;
154*312b00e1SAshish Jangam 	struct watchdog_device *da9055_wdt;
155*312b00e1SAshish Jangam 	int ret;
156*312b00e1SAshish Jangam 
157*312b00e1SAshish Jangam 	driver_data = devm_kzalloc(&pdev->dev, sizeof(*driver_data),
158*312b00e1SAshish Jangam 				   GFP_KERNEL);
159*312b00e1SAshish Jangam 	if (!driver_data) {
160*312b00e1SAshish Jangam 		dev_err(da9055->dev, "Failed to allocate watchdog device\n");
161*312b00e1SAshish Jangam 		return -ENOMEM;
162*312b00e1SAshish Jangam 	}
163*312b00e1SAshish Jangam 
164*312b00e1SAshish Jangam 	driver_data->da9055 = da9055;
165*312b00e1SAshish Jangam 
166*312b00e1SAshish Jangam 	da9055_wdt = &driver_data->wdt;
167*312b00e1SAshish Jangam 
168*312b00e1SAshish Jangam 	da9055_wdt->timeout = DA9055_DEF_TIMEOUT;
169*312b00e1SAshish Jangam 	da9055_wdt->info = &da9055_wdt_info;
170*312b00e1SAshish Jangam 	da9055_wdt->ops = &da9055_wdt_ops;
171*312b00e1SAshish Jangam 	watchdog_set_nowayout(da9055_wdt, nowayout);
172*312b00e1SAshish Jangam 	watchdog_set_drvdata(da9055_wdt, driver_data);
173*312b00e1SAshish Jangam 
174*312b00e1SAshish Jangam 	kref_init(&driver_data->kref);
175*312b00e1SAshish Jangam 
176*312b00e1SAshish Jangam 	ret = da9055_wdt_stop(da9055_wdt);
177*312b00e1SAshish Jangam 	if (ret < 0) {
178*312b00e1SAshish Jangam 		dev_err(&pdev->dev, "Failed to stop watchdog, %d\n", ret);
179*312b00e1SAshish Jangam 		goto err;
180*312b00e1SAshish Jangam 	}
181*312b00e1SAshish Jangam 
182*312b00e1SAshish Jangam 	dev_set_drvdata(&pdev->dev, driver_data);
183*312b00e1SAshish Jangam 
184*312b00e1SAshish Jangam 	ret = watchdog_register_device(&driver_data->wdt);
185*312b00e1SAshish Jangam 	if (ret != 0)
186*312b00e1SAshish Jangam 		dev_err(da9055->dev, "watchdog_register_device() failed: %d\n",
187*312b00e1SAshish Jangam 			ret);
188*312b00e1SAshish Jangam 
189*312b00e1SAshish Jangam err:
190*312b00e1SAshish Jangam 	return ret;
191*312b00e1SAshish Jangam }
192*312b00e1SAshish Jangam 
193*312b00e1SAshish Jangam static int da9055_wdt_remove(struct platform_device *pdev)
194*312b00e1SAshish Jangam {
195*312b00e1SAshish Jangam 	struct da9055_wdt_data *driver_data = dev_get_drvdata(&pdev->dev);
196*312b00e1SAshish Jangam 
197*312b00e1SAshish Jangam 	watchdog_unregister_device(&driver_data->wdt);
198*312b00e1SAshish Jangam 	kref_put(&driver_data->kref, da9055_wdt_release_resources);
199*312b00e1SAshish Jangam 
200*312b00e1SAshish Jangam 	return 0;
201*312b00e1SAshish Jangam }
202*312b00e1SAshish Jangam 
203*312b00e1SAshish Jangam static struct platform_driver da9055_wdt_driver = {
204*312b00e1SAshish Jangam 	.probe = da9055_wdt_probe,
205*312b00e1SAshish Jangam 	.remove = da9055_wdt_remove,
206*312b00e1SAshish Jangam 	.driver = {
207*312b00e1SAshish Jangam 		.name	= "da9055-watchdog",
208*312b00e1SAshish Jangam 	},
209*312b00e1SAshish Jangam };
210*312b00e1SAshish Jangam 
211*312b00e1SAshish Jangam module_platform_driver(da9055_wdt_driver);
212*312b00e1SAshish Jangam 
213*312b00e1SAshish Jangam MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>");
214*312b00e1SAshish Jangam MODULE_DESCRIPTION("DA9055 watchdog");
215*312b00e1SAshish Jangam MODULE_LICENSE("GPL");
216*312b00e1SAshish Jangam MODULE_ALIAS("platform:da9055-watchdog");
217