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 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 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 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 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