xref: /linux/drivers/watchdog/gunyah_wdt.c (revision 9611c0ce215a66770ccbe5c126bf57ba8c31bcad)
1*ca316e14SHrishabh Rajput // SPDX-License-Identifier: GPL-2.0-only
2*ca316e14SHrishabh Rajput /*
3*ca316e14SHrishabh Rajput  * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
4*ca316e14SHrishabh Rajput  */
5*ca316e14SHrishabh Rajput 
6*ca316e14SHrishabh Rajput #include <linux/arm-smccc.h>
7*ca316e14SHrishabh Rajput #include <linux/delay.h>
8*ca316e14SHrishabh Rajput #include <linux/errno.h>
9*ca316e14SHrishabh Rajput #include <linux/kernel.h>
10*ca316e14SHrishabh Rajput #include <linux/mod_devicetable.h>
11*ca316e14SHrishabh Rajput #include <linux/module.h>
12*ca316e14SHrishabh Rajput #include <linux/platform_device.h>
13*ca316e14SHrishabh Rajput #include <linux/watchdog.h>
14*ca316e14SHrishabh Rajput 
15*ca316e14SHrishabh Rajput #define GUNYAH_WDT_SMCCC_CALL_VAL(func_id) \
16*ca316e14SHrishabh Rajput 	ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32,\
17*ca316e14SHrishabh Rajput 			   ARM_SMCCC_OWNER_VENDOR_HYP, func_id)
18*ca316e14SHrishabh Rajput 
19*ca316e14SHrishabh Rajput /* SMCCC function IDs for watchdog operations */
20*ca316e14SHrishabh Rajput #define GUNYAH_WDT_CONTROL   GUNYAH_WDT_SMCCC_CALL_VAL(0x0005)
21*ca316e14SHrishabh Rajput #define GUNYAH_WDT_STATUS    GUNYAH_WDT_SMCCC_CALL_VAL(0x0006)
22*ca316e14SHrishabh Rajput #define GUNYAH_WDT_PING      GUNYAH_WDT_SMCCC_CALL_VAL(0x0007)
23*ca316e14SHrishabh Rajput #define GUNYAH_WDT_SET_TIME  GUNYAH_WDT_SMCCC_CALL_VAL(0x0008)
24*ca316e14SHrishabh Rajput 
25*ca316e14SHrishabh Rajput /*
26*ca316e14SHrishabh Rajput  * Control values for GUNYAH_WDT_CONTROL.
27*ca316e14SHrishabh Rajput  * Bit 0 is used to enable or disable the watchdog. If this bit is set,
28*ca316e14SHrishabh Rajput  * then the watchdog is enabled and vice versa.
29*ca316e14SHrishabh Rajput  * Bit 1 should always be set to 1 as this bit is reserved in Gunyah and
30*ca316e14SHrishabh Rajput  * it's expected to be 1.
31*ca316e14SHrishabh Rajput  */
32*ca316e14SHrishabh Rajput #define WDT_CTRL_ENABLE  (BIT(1) | BIT(0))
33*ca316e14SHrishabh Rajput #define WDT_CTRL_DISABLE BIT(1)
34*ca316e14SHrishabh Rajput 
35*ca316e14SHrishabh Rajput enum gunyah_error {
36*ca316e14SHrishabh Rajput 	GUNYAH_ERROR_OK				= 0,
37*ca316e14SHrishabh Rajput 	GUNYAH_ERROR_UNIMPLEMENTED		= -1,
38*ca316e14SHrishabh Rajput 	GUNYAH_ERROR_ARG_INVAL			= 1,
39*ca316e14SHrishabh Rajput };
40*ca316e14SHrishabh Rajput 
41*ca316e14SHrishabh Rajput /**
42*ca316e14SHrishabh Rajput  * gunyah_error_remap() - Remap Gunyah hypervisor errors into a Linux error code
43*ca316e14SHrishabh Rajput  * @gunyah_error: Gunyah hypercall return value
44*ca316e14SHrishabh Rajput  */
45*ca316e14SHrishabh Rajput static inline int gunyah_error_remap(enum gunyah_error gunyah_error)
46*ca316e14SHrishabh Rajput {
47*ca316e14SHrishabh Rajput 	switch (gunyah_error) {
48*ca316e14SHrishabh Rajput 	case GUNYAH_ERROR_OK:
49*ca316e14SHrishabh Rajput 		return 0;
50*ca316e14SHrishabh Rajput 	case GUNYAH_ERROR_UNIMPLEMENTED:
51*ca316e14SHrishabh Rajput 		return -EOPNOTSUPP;
52*ca316e14SHrishabh Rajput 	default:
53*ca316e14SHrishabh Rajput 		return -EINVAL;
54*ca316e14SHrishabh Rajput 	}
55*ca316e14SHrishabh Rajput }
56*ca316e14SHrishabh Rajput 
57*ca316e14SHrishabh Rajput static int gunyah_wdt_call(unsigned long func_id, unsigned long arg1,
58*ca316e14SHrishabh Rajput 			   unsigned long arg2)
59*ca316e14SHrishabh Rajput {
60*ca316e14SHrishabh Rajput 	struct arm_smccc_res res;
61*ca316e14SHrishabh Rajput 
62*ca316e14SHrishabh Rajput 	arm_smccc_1_1_smc(func_id, arg1, arg2, &res);
63*ca316e14SHrishabh Rajput 	return gunyah_error_remap(res.a0);
64*ca316e14SHrishabh Rajput }
65*ca316e14SHrishabh Rajput 
66*ca316e14SHrishabh Rajput static int gunyah_wdt_start(struct watchdog_device *wdd)
67*ca316e14SHrishabh Rajput {
68*ca316e14SHrishabh Rajput 	unsigned int timeout_ms;
69*ca316e14SHrishabh Rajput 	struct device *dev = wdd->parent;
70*ca316e14SHrishabh Rajput 	int ret;
71*ca316e14SHrishabh Rajput 
72*ca316e14SHrishabh Rajput 	ret = gunyah_wdt_call(GUNYAH_WDT_CONTROL, WDT_CTRL_DISABLE, 0);
73*ca316e14SHrishabh Rajput 	if (ret && watchdog_active(wdd)) {
74*ca316e14SHrishabh Rajput 		dev_err(dev, "%s: Failed to stop gunyah wdt %d\n", __func__, ret);
75*ca316e14SHrishabh Rajput 		return ret;
76*ca316e14SHrishabh Rajput 	}
77*ca316e14SHrishabh Rajput 
78*ca316e14SHrishabh Rajput 	timeout_ms = wdd->timeout * 1000;
79*ca316e14SHrishabh Rajput 	ret = gunyah_wdt_call(GUNYAH_WDT_SET_TIME, timeout_ms, timeout_ms);
80*ca316e14SHrishabh Rajput 	if (ret) {
81*ca316e14SHrishabh Rajput 		dev_err(dev, "%s: Failed to set timeout for gunyah wdt %d\n",
82*ca316e14SHrishabh Rajput 			__func__, ret);
83*ca316e14SHrishabh Rajput 		return ret;
84*ca316e14SHrishabh Rajput 	}
85*ca316e14SHrishabh Rajput 
86*ca316e14SHrishabh Rajput 	ret = gunyah_wdt_call(GUNYAH_WDT_CONTROL, WDT_CTRL_ENABLE, 0);
87*ca316e14SHrishabh Rajput 	if (ret)
88*ca316e14SHrishabh Rajput 		dev_err(dev, "%s: Failed to start gunyah wdt %d\n", __func__, ret);
89*ca316e14SHrishabh Rajput 
90*ca316e14SHrishabh Rajput 	return ret;
91*ca316e14SHrishabh Rajput }
92*ca316e14SHrishabh Rajput 
93*ca316e14SHrishabh Rajput static int gunyah_wdt_stop(struct watchdog_device *wdd)
94*ca316e14SHrishabh Rajput {
95*ca316e14SHrishabh Rajput 	return gunyah_wdt_call(GUNYAH_WDT_CONTROL, WDT_CTRL_DISABLE, 0);
96*ca316e14SHrishabh Rajput }
97*ca316e14SHrishabh Rajput 
98*ca316e14SHrishabh Rajput static int gunyah_wdt_ping(struct watchdog_device *wdd)
99*ca316e14SHrishabh Rajput {
100*ca316e14SHrishabh Rajput 	return gunyah_wdt_call(GUNYAH_WDT_PING, 0, 0);
101*ca316e14SHrishabh Rajput }
102*ca316e14SHrishabh Rajput 
103*ca316e14SHrishabh Rajput static int gunyah_wdt_set_timeout(struct watchdog_device *wdd,
104*ca316e14SHrishabh Rajput 				  unsigned int timeout_sec)
105*ca316e14SHrishabh Rajput {
106*ca316e14SHrishabh Rajput 	wdd->timeout = timeout_sec;
107*ca316e14SHrishabh Rajput 
108*ca316e14SHrishabh Rajput 	if (watchdog_active(wdd))
109*ca316e14SHrishabh Rajput 		return gunyah_wdt_start(wdd);
110*ca316e14SHrishabh Rajput 
111*ca316e14SHrishabh Rajput 	return 0;
112*ca316e14SHrishabh Rajput }
113*ca316e14SHrishabh Rajput 
114*ca316e14SHrishabh Rajput static int gunyah_wdt_get_time_since_last_ping(void)
115*ca316e14SHrishabh Rajput {
116*ca316e14SHrishabh Rajput 	struct arm_smccc_res res;
117*ca316e14SHrishabh Rajput 
118*ca316e14SHrishabh Rajput 	arm_smccc_1_1_smc(GUNYAH_WDT_STATUS, 0, 0, &res);
119*ca316e14SHrishabh Rajput 	if (res.a0)
120*ca316e14SHrishabh Rajput 		return gunyah_error_remap(res.a0);
121*ca316e14SHrishabh Rajput 
122*ca316e14SHrishabh Rajput 	return res.a2 / 1000;
123*ca316e14SHrishabh Rajput }
124*ca316e14SHrishabh Rajput 
125*ca316e14SHrishabh Rajput static unsigned int gunyah_wdt_get_timeleft(struct watchdog_device *wdd)
126*ca316e14SHrishabh Rajput {
127*ca316e14SHrishabh Rajput 	int seconds_since_last_ping;
128*ca316e14SHrishabh Rajput 
129*ca316e14SHrishabh Rajput 	seconds_since_last_ping = gunyah_wdt_get_time_since_last_ping();
130*ca316e14SHrishabh Rajput 	if (seconds_since_last_ping < 0 ||
131*ca316e14SHrishabh Rajput 	    seconds_since_last_ping > wdd->timeout)
132*ca316e14SHrishabh Rajput 		return 0;
133*ca316e14SHrishabh Rajput 
134*ca316e14SHrishabh Rajput 	return wdd->timeout - seconds_since_last_ping;
135*ca316e14SHrishabh Rajput }
136*ca316e14SHrishabh Rajput 
137*ca316e14SHrishabh Rajput static int gunyah_wdt_restart(struct watchdog_device *wdd,
138*ca316e14SHrishabh Rajput 			      unsigned long action, void *data)
139*ca316e14SHrishabh Rajput {
140*ca316e14SHrishabh Rajput 	/* Set timeout to 1ms and send a ping */
141*ca316e14SHrishabh Rajput 	gunyah_wdt_call(GUNYAH_WDT_CONTROL, WDT_CTRL_DISABLE, 0);
142*ca316e14SHrishabh Rajput 	gunyah_wdt_call(GUNYAH_WDT_SET_TIME, 1, 1);
143*ca316e14SHrishabh Rajput 	gunyah_wdt_call(GUNYAH_WDT_CONTROL, WDT_CTRL_ENABLE, 0);
144*ca316e14SHrishabh Rajput 	gunyah_wdt_call(GUNYAH_WDT_PING, 0, 0);
145*ca316e14SHrishabh Rajput 
146*ca316e14SHrishabh Rajput 	/* Wait to make sure reset occurs */
147*ca316e14SHrishabh Rajput 	mdelay(100);
148*ca316e14SHrishabh Rajput 
149*ca316e14SHrishabh Rajput 	return 0;
150*ca316e14SHrishabh Rajput }
151*ca316e14SHrishabh Rajput 
152*ca316e14SHrishabh Rajput static const struct watchdog_info gunyah_wdt_info = {
153*ca316e14SHrishabh Rajput 	.identity = "Gunyah Watchdog",
154*ca316e14SHrishabh Rajput 	.options = WDIOF_SETTIMEOUT
155*ca316e14SHrishabh Rajput 		 | WDIOF_KEEPALIVEPING
156*ca316e14SHrishabh Rajput 		 | WDIOF_MAGICCLOSE,
157*ca316e14SHrishabh Rajput };
158*ca316e14SHrishabh Rajput 
159*ca316e14SHrishabh Rajput static const struct watchdog_ops gunyah_wdt_ops = {
160*ca316e14SHrishabh Rajput 	.owner = THIS_MODULE,
161*ca316e14SHrishabh Rajput 	.start = gunyah_wdt_start,
162*ca316e14SHrishabh Rajput 	.stop = gunyah_wdt_stop,
163*ca316e14SHrishabh Rajput 	.ping = gunyah_wdt_ping,
164*ca316e14SHrishabh Rajput 	.set_timeout = gunyah_wdt_set_timeout,
165*ca316e14SHrishabh Rajput 	.get_timeleft = gunyah_wdt_get_timeleft,
166*ca316e14SHrishabh Rajput 	.restart = gunyah_wdt_restart
167*ca316e14SHrishabh Rajput };
168*ca316e14SHrishabh Rajput 
169*ca316e14SHrishabh Rajput static int gunyah_wdt_probe(struct platform_device *pdev)
170*ca316e14SHrishabh Rajput {
171*ca316e14SHrishabh Rajput 	struct watchdog_device *wdd;
172*ca316e14SHrishabh Rajput 	struct device *dev = &pdev->dev;
173*ca316e14SHrishabh Rajput 	int ret;
174*ca316e14SHrishabh Rajput 
175*ca316e14SHrishabh Rajput 	ret = gunyah_wdt_call(GUNYAH_WDT_STATUS, 0, 0);
176*ca316e14SHrishabh Rajput 	if (ret == -EOPNOTSUPP)
177*ca316e14SHrishabh Rajput 		return -ENODEV;
178*ca316e14SHrishabh Rajput 
179*ca316e14SHrishabh Rajput 	if (ret)
180*ca316e14SHrishabh Rajput 		return dev_err_probe(dev, ret, "status check failed\n");
181*ca316e14SHrishabh Rajput 
182*ca316e14SHrishabh Rajput 	wdd = devm_kzalloc(dev, sizeof(*wdd), GFP_KERNEL);
183*ca316e14SHrishabh Rajput 	if (!wdd)
184*ca316e14SHrishabh Rajput 		return -ENOMEM;
185*ca316e14SHrishabh Rajput 
186*ca316e14SHrishabh Rajput 	wdd->info = &gunyah_wdt_info;
187*ca316e14SHrishabh Rajput 	wdd->ops = &gunyah_wdt_ops;
188*ca316e14SHrishabh Rajput 	wdd->parent = dev;
189*ca316e14SHrishabh Rajput 
190*ca316e14SHrishabh Rajput 	/*
191*ca316e14SHrishabh Rajput 	 * Although Gunyah expects 16-bit unsigned int values as timeout values
192*ca316e14SHrishabh Rajput 	 * in milliseconds, values above 0x8000 are reserved. This limits the
193*ca316e14SHrishabh Rajput 	 * max timeout value to 32 seconds.
194*ca316e14SHrishabh Rajput 	 */
195*ca316e14SHrishabh Rajput 	wdd->max_timeout = 32; /* seconds */
196*ca316e14SHrishabh Rajput 	wdd->min_timeout = 1; /* seconds */
197*ca316e14SHrishabh Rajput 	wdd->timeout = wdd->max_timeout;
198*ca316e14SHrishabh Rajput 
199*ca316e14SHrishabh Rajput 	gunyah_wdt_stop(wdd);
200*ca316e14SHrishabh Rajput 	platform_set_drvdata(pdev, wdd);
201*ca316e14SHrishabh Rajput 	watchdog_set_restart_priority(wdd, 0);
202*ca316e14SHrishabh Rajput 
203*ca316e14SHrishabh Rajput 	return devm_watchdog_register_device(dev, wdd);
204*ca316e14SHrishabh Rajput }
205*ca316e14SHrishabh Rajput 
206*ca316e14SHrishabh Rajput static void gunyah_wdt_remove(struct platform_device *pdev)
207*ca316e14SHrishabh Rajput {
208*ca316e14SHrishabh Rajput 	struct watchdog_device *wdd = platform_get_drvdata(pdev);
209*ca316e14SHrishabh Rajput 
210*ca316e14SHrishabh Rajput 	gunyah_wdt_stop(wdd);
211*ca316e14SHrishabh Rajput }
212*ca316e14SHrishabh Rajput 
213*ca316e14SHrishabh Rajput static int gunyah_wdt_suspend(struct device *dev)
214*ca316e14SHrishabh Rajput {
215*ca316e14SHrishabh Rajput 	struct watchdog_device *wdd = dev_get_drvdata(dev);
216*ca316e14SHrishabh Rajput 
217*ca316e14SHrishabh Rajput 	if (watchdog_active(wdd))
218*ca316e14SHrishabh Rajput 		gunyah_wdt_stop(wdd);
219*ca316e14SHrishabh Rajput 
220*ca316e14SHrishabh Rajput 	return 0;
221*ca316e14SHrishabh Rajput }
222*ca316e14SHrishabh Rajput 
223*ca316e14SHrishabh Rajput static int gunyah_wdt_resume(struct device *dev)
224*ca316e14SHrishabh Rajput {
225*ca316e14SHrishabh Rajput 	struct watchdog_device *wdd = dev_get_drvdata(dev);
226*ca316e14SHrishabh Rajput 
227*ca316e14SHrishabh Rajput 	if (watchdog_active(wdd))
228*ca316e14SHrishabh Rajput 		gunyah_wdt_start(wdd);
229*ca316e14SHrishabh Rajput 
230*ca316e14SHrishabh Rajput 	return 0;
231*ca316e14SHrishabh Rajput }
232*ca316e14SHrishabh Rajput 
233*ca316e14SHrishabh Rajput static DEFINE_SIMPLE_DEV_PM_OPS(gunyah_wdt_pm_ops, gunyah_wdt_suspend, gunyah_wdt_resume);
234*ca316e14SHrishabh Rajput 
235*ca316e14SHrishabh Rajput /*
236*ca316e14SHrishabh Rajput  * Gunyah watchdog is a vendor-specific hypervisor interface provided by the
237*ca316e14SHrishabh Rajput  * Gunyah hypervisor. Using QCOM SCM driver to detect Gunyah watchdog SMCCC
238*ca316e14SHrishabh Rajput  * hypervisor service and register platform device when the service is available
239*ca316e14SHrishabh Rajput  * allows this driver to operate independently of the devicetree and avoids
240*ca316e14SHrishabh Rajput  * adding the non-hardware nodes to the devicetree.
241*ca316e14SHrishabh Rajput  */
242*ca316e14SHrishabh Rajput static const struct platform_device_id gunyah_wdt_id[] = {
243*ca316e14SHrishabh Rajput 	{ .name = "gunyah-wdt" },
244*ca316e14SHrishabh Rajput 	{}
245*ca316e14SHrishabh Rajput };
246*ca316e14SHrishabh Rajput MODULE_DEVICE_TABLE(platform, gunyah_wdt_id);
247*ca316e14SHrishabh Rajput 
248*ca316e14SHrishabh Rajput static struct platform_driver gunyah_wdt_driver = {
249*ca316e14SHrishabh Rajput 	.driver = {
250*ca316e14SHrishabh Rajput 		.name = "gunyah-wdt",
251*ca316e14SHrishabh Rajput 		.pm = pm_sleep_ptr(&gunyah_wdt_pm_ops),
252*ca316e14SHrishabh Rajput 	},
253*ca316e14SHrishabh Rajput 	.id_table = gunyah_wdt_id,
254*ca316e14SHrishabh Rajput 	.probe = gunyah_wdt_probe,
255*ca316e14SHrishabh Rajput 	.remove = gunyah_wdt_remove,
256*ca316e14SHrishabh Rajput };
257*ca316e14SHrishabh Rajput 
258*ca316e14SHrishabh Rajput module_platform_driver(gunyah_wdt_driver);
259*ca316e14SHrishabh Rajput 
260*ca316e14SHrishabh Rajput MODULE_DESCRIPTION("Gunyah Watchdog Driver");
261*ca316e14SHrishabh Rajput MODULE_LICENSE("GPL");
262