1 /* $NetBS$ */ 2 /* $OpenBSD: qcipcc.c,v 1.2 2023/05/19 20:54:55 patrick Exp $ */ 3 /* 4 * Copyright (c) 2023 Patrick Wildt <patrick@blueri.se> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/param.h> 20 #include <sys/systm.h> 21 #include <sys/device.h> 22 #include <sys/kmem.h> 23 24 #include <dev/acpi/acpivar.h> 25 #include <dev/acpi/acpi_intr.h> 26 #include <dev/acpi/qcomipcc.h> 27 28 #define IPCC_SEND_ID 0x0c 29 #define IPCC_RECV_ID 0x10 30 #define IPCC_RECV_SIGNAL_ENABLE 0x14 31 #define IPCC_RECV_SIGNAL_DISABLE 0x18 32 #define IPCC_RECV_SIGNAL_CLEAR 0x1c 33 34 #define IPCC_SIGNAL_ID_MASK __BITS(15,0) 35 #define IPCC_CLIENT_ID_MASK __BITS(31,16) 36 37 #define HREAD4(sc, reg) \ 38 bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)) 39 #define HWRITE4(sc, reg, val) \ 40 bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val)) 41 42 struct qcipcc_intrhand { 43 TAILQ_ENTRY(qcipcc_intrhand) ih_q; 44 int (*ih_func)(void *); 45 void *ih_arg; 46 void *ih_sc; 47 uint32_t ih_client_id; 48 uint32_t ih_signal_id; 49 }; 50 51 struct qcipcc_softc { 52 device_t sc_dev; 53 bus_space_tag_t sc_iot; 54 bus_space_handle_t sc_ioh; 55 56 void *sc_ih; 57 58 TAILQ_HEAD(,qcipcc_intrhand) sc_intrq; 59 }; 60 61 static struct qcipcc_softc *qcipcc = NULL; 62 63 struct qcipcc_channel { 64 struct qcipcc_softc *ch_sc; 65 uint32_t ch_client_id; 66 uint32_t ch_signal_id; 67 }; 68 69 #define QCIPCC_8380_BASE 0x00408000 70 #define QCIPCC_SIZE 0x1000 71 72 static const struct device_compatible_entry compat_data[] = { 73 { .compat = "QCOM06C2", .value = QCIPCC_8380_BASE }, 74 DEVICE_COMPAT_EOL 75 }; 76 77 static int qcipcc_match(device_t, cfdata_t, void *); 78 static void qcipcc_attach(device_t, device_t, void *); 79 static int qcipcc_intr(void *); 80 81 CFATTACH_DECL_NEW(qcomipcc, sizeof(struct qcipcc_softc), 82 qcipcc_match, qcipcc_attach, NULL, NULL); 83 84 static int 85 qcipcc_match(device_t parent, cfdata_t match, void *aux) 86 { 87 struct acpi_attach_args *aa = aux; 88 89 return acpi_compatible_match(aa, compat_data); 90 } 91 92 static void 93 qcipcc_attach(device_t parent, device_t self, void *aux) 94 { 95 struct qcipcc_softc *sc = device_private(self); 96 struct acpi_attach_args *aa = aux; 97 ACPI_HANDLE hdl; 98 struct acpi_resources res; 99 struct acpi_irq *irq; 100 bus_addr_t base_addr; 101 ACPI_STATUS rv; 102 103 if (qcipcc != NULL) { 104 aprint_error(": already attached!\n"); 105 return; 106 } 107 108 hdl = aa->aa_node->ad_handle; 109 rv = acpi_resource_parse(self, hdl, "_CRS", &res, 110 &acpi_resource_parse_ops_default); 111 if (ACPI_FAILURE(rv)) { 112 return; 113 } 114 115 irq = acpi_res_irq(&res, 0); 116 if (irq == NULL) { 117 aprint_error_dev(self, "couldn't find irq resource\n"); 118 goto done; 119 } 120 121 base_addr = acpi_compatible_lookup(aa, compat_data)->value; 122 123 sc->sc_dev = self; 124 sc->sc_iot = aa->aa_memt; 125 if (bus_space_map(sc->sc_iot, base_addr, QCIPCC_SIZE, 126 BUS_SPACE_MAP_NONPOSTED, &sc->sc_ioh) != 0) { 127 aprint_error_dev(self, "couldn't map registers\n"); 128 goto done; 129 } 130 131 TAILQ_INIT(&sc->sc_intrq); 132 133 sc->sc_ih = acpi_intr_establish(self, 134 (uint64_t)(uintptr_t)hdl, 135 IPL_VM, false, qcipcc_intr, sc, device_xname(self)); 136 if (sc->sc_ih == NULL) { 137 aprint_error_dev(self, 138 "couldn't establish interrupt\n"); 139 goto done; 140 } 141 142 qcipcc = sc; 143 144 done: 145 acpi_resource_cleanup(&res); 146 } 147 148 static int 149 qcipcc_intr(void *arg) 150 { 151 struct qcipcc_softc *sc = arg; 152 struct qcipcc_intrhand *ih; 153 uint16_t client_id, signal_id; 154 uint32_t reg; 155 int handled = 0; 156 157 while ((reg = HREAD4(sc, IPCC_RECV_ID)) != ~0) { 158 HWRITE4(sc, IPCC_RECV_SIGNAL_CLEAR, reg); 159 160 client_id = __SHIFTOUT(reg, IPCC_CLIENT_ID_MASK); 161 signal_id = __SHIFTOUT(reg, IPCC_SIGNAL_ID_MASK); 162 163 TAILQ_FOREACH(ih, &sc->sc_intrq, ih_q) { 164 if (ih->ih_client_id != client_id || 165 ih->ih_signal_id != signal_id) 166 continue; 167 ih->ih_func(ih->ih_arg); 168 handled = 1; 169 } 170 } 171 172 return handled; 173 } 174 175 void * 176 qcipcc_intr_establish(uint16_t client_id, uint16_t signal_id, int ipl, 177 int (*func)(void *), void *arg) 178 { 179 struct qcipcc_softc *sc = qcipcc; 180 struct qcipcc_intrhand *ih; 181 182 if (sc == NULL) { 183 return NULL; 184 } 185 186 ih = kmem_zalloc(sizeof(*ih), KM_SLEEP); 187 ih->ih_func = func; 188 ih->ih_arg = arg; 189 ih->ih_sc = sc; 190 ih->ih_client_id = client_id; 191 ih->ih_signal_id = signal_id; 192 TAILQ_INSERT_TAIL(&sc->sc_intrq, ih, ih_q); 193 194 qcipcc_intr_enable(ih); 195 196 return ih; 197 } 198 199 void 200 qcipcc_intr_disestablish(void *cookie) 201 { 202 struct qcipcc_intrhand *ih = cookie; 203 struct qcipcc_softc *sc = ih->ih_sc; 204 205 qcipcc_intr_disable(ih); 206 207 TAILQ_REMOVE(&sc->sc_intrq, ih, ih_q); 208 kmem_free(ih, sizeof(*ih)); 209 } 210 211 void 212 qcipcc_intr_enable(void *cookie) 213 { 214 struct qcipcc_intrhand *ih = cookie; 215 struct qcipcc_softc *sc = ih->ih_sc; 216 217 HWRITE4(sc, IPCC_RECV_SIGNAL_ENABLE, 218 __SHIFTIN(ih->ih_client_id, IPCC_CLIENT_ID_MASK) | 219 __SHIFTIN(ih->ih_signal_id, IPCC_SIGNAL_ID_MASK)); 220 } 221 222 void 223 qcipcc_intr_disable(void *cookie) 224 { 225 struct qcipcc_intrhand *ih = cookie; 226 struct qcipcc_softc *sc = ih->ih_sc; 227 228 HWRITE4(sc, IPCC_RECV_SIGNAL_DISABLE, 229 __SHIFTIN(ih->ih_client_id, IPCC_CLIENT_ID_MASK) | 230 __SHIFTIN(ih->ih_signal_id, IPCC_SIGNAL_ID_MASK)); 231 } 232 233 void * 234 qcipcc_channel(uint16_t client_id, uint16_t signal_id) 235 { 236 struct qcipcc_softc *sc = qcipcc; 237 struct qcipcc_channel *ch; 238 239 if (qcipcc == NULL) { 240 return NULL; 241 } 242 243 ch = kmem_zalloc(sizeof(*ch), KM_SLEEP); 244 ch->ch_sc = sc; 245 ch->ch_client_id = client_id; 246 ch->ch_signal_id = signal_id; 247 248 return ch; 249 } 250 251 int 252 qcipcc_send(void *cookie) 253 { 254 struct qcipcc_channel *ch = cookie; 255 struct qcipcc_softc *sc = ch->ch_sc; 256 257 HWRITE4(sc, IPCC_SEND_ID, 258 __SHIFTIN(ch->ch_client_id, IPCC_CLIENT_ID_MASK) | 259 __SHIFTIN(ch->ch_signal_id, IPCC_SIGNAL_ID_MASK)); 260 261 return 0; 262 } 263