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