xref: /openbsd/sys/dev/fdt/qcpon.c (revision 5ecd7399)
1 /*	$OpenBSD: qcpon.c,v 1.6 2025/01/03 14:14:49 kettenis Exp $	*/
2 /*
3  * Copyright (c) 2022 Patrick Wildt <patrick@blueri.se>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/param.h>
19 #include <sys/malloc.h>
20 #include <sys/systm.h>
21 #include <sys/task.h>
22 #include <sys/proc.h>
23 #include <sys/signalvar.h>
24 
25 #include <machine/bus.h>
26 #include <machine/fdt.h>
27 
28 #include <dev/fdt/spmivar.h>
29 
30 #include <dev/ofw/openfirm.h>
31 #include <dev/ofw/fdt.h>
32 
33 /* Registers. */
34 #define PON_RT_STS		0x10
35 #define  PON_PMK8350_KPDPWR_N_SET	(1U << 7)
36 
37 struct qcpon_softc {
38 	struct device		sc_dev;
39 	int			sc_node;
40 
41 	spmi_tag_t		sc_tag;
42 	int8_t			sc_sid;
43 	uint16_t		sc_addr;
44 
45 	void			*sc_pwrkey_ih;
46 	uint32_t		sc_last_sts;
47 	struct task		sc_powerdown_task;
48 };
49 
50 int	qcpon_match(struct device *, void *, void *);
51 void	qcpon_attach(struct device *, struct device *, void *);
52 
53 int	qcpon_pwrkey_intr(void *);
54 void	qcpon_powerdown_task(void *);
55 
56 const struct cfattach qcpon_ca = {
57 	sizeof(struct qcpon_softc), qcpon_match, qcpon_attach
58 };
59 
60 struct cfdriver qcpon_cd = {
61 	NULL, "qcpon", DV_DULL
62 };
63 
64 int
qcpon_match(struct device * parent,void * match,void * aux)65 qcpon_match(struct device *parent, void *match, void *aux)
66 {
67 	struct spmi_attach_args *saa = aux;
68 
69 	return (OF_is_compatible(saa->sa_node, "qcom,pm8998-pon") ||
70 	    OF_is_compatible(saa->sa_node, "qcom,pmk8350-pon"));
71 }
72 
73 void
qcpon_attach(struct device * parent,struct device * self,void * aux)74 qcpon_attach(struct device *parent, struct device *self, void *aux)
75 {
76 	struct spmi_attach_args *saa = aux;
77 	struct qcpon_softc *sc = (struct qcpon_softc *)self;
78 	uint32_t reg[2];
79 	int node;
80 
81 	if (OF_getpropintarray(saa->sa_node, "reg",
82 	    reg, sizeof(reg)) != sizeof(reg)) {
83 		printf(": can't find registers\n");
84 		return;
85 	}
86 
87 	sc->sc_node = saa->sa_node;
88 	sc->sc_tag = saa->sa_tag;
89 	sc->sc_sid = saa->sa_sid;
90 	sc->sc_addr = reg[0];
91 
92 	task_set(&sc->sc_powerdown_task, qcpon_powerdown_task, sc);
93 
94 	printf("\n");
95 
96 	for (node = OF_child(saa->sa_node); node; node = OF_peer(node)) {
97 		if (OF_is_compatible(node, "qcom,pmk8350-pwrkey")) {
98 			sc->sc_pwrkey_ih = fdt_intr_establish(node,
99 			    IPL_BIO | IPL_WAKEUP, qcpon_pwrkey_intr, sc,
100 			    sc->sc_dev.dv_xname);
101 			if (sc->sc_pwrkey_ih == NULL) {
102 				printf("%s: can't establish interrupt\n",
103 				    sc->sc_dev.dv_xname);
104 				continue;
105 			}
106 #ifdef SUSPEND
107 			device_register_wakeup(&sc->sc_dev);
108 #endif
109 		}
110 	}
111 }
112 
113 int
qcpon_pwrkey_intr(void * arg)114 qcpon_pwrkey_intr(void *arg)
115 {
116 	struct qcpon_softc *sc = arg;
117 #ifdef SUSPEND
118 	extern int cpu_suspended;
119 #endif
120 	uint32_t sts;
121 	int error;
122 
123 #ifdef SUSPEND
124 	if (cpu_suspended) {
125 		cpu_suspended = 0;
126 		return 1;
127 	}
128 #endif
129 
130 	error = spmi_cmd_read(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_READL,
131 	    sc->sc_addr + PON_RT_STS, &sts, sizeof(sts));
132 	if (error)
133 		return 0;
134 
135 	/* Ignore presses, handle releases. */
136 	if ((sc->sc_last_sts & PON_PMK8350_KPDPWR_N_SET) &&
137 	    (sts & PON_PMK8350_KPDPWR_N_SET) == 0)
138 		task_add(systq, &sc->sc_powerdown_task);
139 
140 	sc->sc_last_sts = sts;
141 	return 1;
142 }
143 
144 void
qcpon_powerdown_task(void * arg)145 qcpon_powerdown_task(void *arg)
146 {
147 	extern int allowpowerdown;
148 
149 	if (allowpowerdown == 1) {
150 		allowpowerdown = 0;
151 		prsignal(initprocess, SIGUSR2);
152 	}
153 }
154