1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2021 Ampere Computing LLC 5 * Copyright (c) 2022 Arm Ltd 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 * 28 * $FreeBSD$ 29 */ 30 31 #include <sys/cdefs.h> 32 __FBSDID("$FreeBSD$"); 33 34 #include "opt_hwpmc_hooks.h" 35 #include "opt_acpi.h" 36 37 /* 38 * This depends on ACPI, but is built unconditionally in the hwpmc module. 39 */ 40 #ifdef DEV_ACPI 41 #include <sys/param.h> 42 #include <sys/bus.h> 43 #include <sys/module.h> 44 #include <sys/rman.h> 45 #include <sys/pmc.h> 46 #include <sys/pmckern.h> 47 48 #include <machine/bus.h> 49 #include <machine/cpu.h> 50 51 #include <contrib/dev/acpica/include/acpi.h> 52 #include <dev/acpica/acpivar.h> 53 54 #include <dev/hwpmc/pmu_dmc620_reg.h> 55 56 static char *pmu_dmc620_ids[] = { 57 "ARMHD620", 58 NULL 59 }; 60 61 static struct resource_spec pmu_dmc620_res_spec[] = { 62 { SYS_RES_MEMORY, 0, RF_ACTIVE }, 63 { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE }, 64 { -1, 0 } 65 }; 66 67 struct pmu_dmc620_softc { 68 device_t sc_dev; 69 int sc_unit; 70 int sc_domain; 71 struct resource *sc_res[2]; 72 void *sc_ih; 73 uint32_t sc_clkdiv2_conters_hi[DMC620_CLKDIV2_COUNTERS_N]; 74 uint32_t sc_clk_conters_hi[DMC620_CLK_COUNTERS_N]; 75 uint32_t sc_saved_control[DMC620_COUNTERS_N]; 76 }; 77 78 #define RD4(sc, r) bus_read_4((sc)->sc_res[0], (r)) 79 #define WR4(sc, r, v) bus_write_4((sc)->sc_res[0], (r), (v)) 80 #define MD4(sc, r, c, s) WR4((sc), (r), RD4((sc), (r)) & ~(c) | (s)) 81 82 #define CD2MD4(sc, u, r, c, s) MD4((sc), DMC620_CLKDIV2_REG((u), (r)), (c), (s)) 83 #define CMD4(sc, u, r, c, s) MD4((sc), DMC620_CLK_REG((u), (r)), (c), (s)) 84 85 static int pmu_dmc620_counter_overflow_intr(void *arg); 86 87 uint32_t 88 pmu_dmc620_rd4(void *arg, u_int cntr, off_t reg) 89 { 90 struct pmu_dmc620_softc *sc; 91 uint32_t val; 92 93 sc = (struct pmu_dmc620_softc *)arg; 94 KASSERT(cntr < DMC620_COUNTERS_N, ("Wrong counter unit %d", cntr)); 95 96 val = RD4(sc, DMC620_REG(cntr, reg)); 97 return (val); 98 } 99 100 void 101 pmu_dmc620_wr4(void *arg, u_int cntr, off_t reg, uint32_t val) 102 { 103 struct pmu_dmc620_softc *sc; 104 105 sc = (struct pmu_dmc620_softc *)arg; 106 KASSERT(cntr < DMC620_COUNTERS_N, ("Wrong counter unit %d", cntr)); 107 108 WR4(sc, DMC620_REG(cntr, reg), val); 109 } 110 111 static int 112 pmu_dmc620_acpi_probe(device_t dev) 113 { 114 int err; 115 116 err = ACPI_ID_PROBE(device_get_parent(dev), dev, pmu_dmc620_ids, NULL); 117 if (err <= 0) 118 device_set_desc(dev, "ARM DMC-620 Memory Controller PMU"); 119 120 return (err); 121 } 122 123 static int 124 pmu_dmc620_acpi_attach(device_t dev) 125 { 126 struct pmu_dmc620_softc *sc; 127 int domain, i, u; 128 const char *dname; 129 130 dname = device_get_name(dev); 131 sc = device_get_softc(dev); 132 sc->sc_dev = dev; 133 u = device_get_unit(dev); 134 sc->sc_unit = u; 135 136 /* 137 * Ampere Altra support NUMA emulation, but DMC-620 PMU units have no 138 * mapping. Emulate this with kenv/hints. 139 * Format "hint.pmu_dmc620.3.domain=1". 140 */ 141 if ((resource_int_value(dname, u, "domain", &domain) == 0 || 142 bus_get_domain(dev, &domain) == 0) && domain < MAXMEMDOM) { 143 sc->sc_domain = domain; 144 } 145 device_printf(dev, "domain=%d\n", domain); 146 147 i = bus_alloc_resources(dev, pmu_dmc620_res_spec, sc->sc_res); 148 if (i != 0) { 149 device_printf(dev, "cannot allocate resources for device (%d)\n", 150 i); 151 return (i); 152 } 153 /* Disable counter before enable interrupt. */ 154 for (i = 0; i < DMC620_CLKDIV2_COUNTERS_N; i++) { 155 CD2MD4(sc, i, DMC620_COUNTER_CONTROL, 156 DMC620_COUNTER_CONTROL_ENABLE, 0); 157 } 158 for (i = 0; i < DMC620_CLK_COUNTERS_N; i++) { 159 CMD4(sc, i, DMC620_COUNTER_CONTROL, 160 DMC620_COUNTER_CONTROL_ENABLE, 0); 161 } 162 163 /* Clear intr status. */ 164 WR4(sc, DMC620_OVERFLOW_STATUS_CLKDIV2, 0); 165 WR4(sc, DMC620_OVERFLOW_STATUS_CLK, 0); 166 167 if (sc->sc_res[1] != NULL && bus_setup_intr(dev, sc->sc_res[1], 168 INTR_TYPE_MISC | INTR_MPSAFE, pmu_dmc620_counter_overflow_intr, 169 NULL, sc, &sc->sc_ih)) { 170 bus_release_resources(dev, pmu_dmc620_res_spec, sc->sc_res); 171 device_printf(dev, "cannot setup interrupt handler\n"); 172 return (ENXIO); 173 } 174 dmc620_pmc_register(u, sc, domain); 175 return (0); 176 } 177 178 static int 179 pmu_dmc620_acpi_detach(device_t dev) 180 { 181 struct pmu_dmc620_softc *sc; 182 183 sc = device_get_softc(dev); 184 dmc620_pmc_unregister(device_get_unit(dev)); 185 if (sc->sc_res[1] != NULL) { 186 bus_teardown_intr(dev, sc->sc_res[1], sc->sc_ih); 187 } 188 bus_release_resources(dev, pmu_dmc620_res_spec, sc->sc_res); 189 190 return (0); 191 } 192 193 static void 194 pmu_dmc620_clkdiv2_overflow(struct trapframe *tf, struct pmu_dmc620_softc *sc, 195 u_int i) 196 { 197 198 atomic_add_32(&sc->sc_clkdiv2_conters_hi[i], 1); 199 /* Call dmc620 handler directly, because hook busy by arm64_intr. */ 200 dmc620_intr(tf, PMC_CLASS_DMC620_PMU_CD2, sc->sc_unit, i); 201 } 202 203 static void 204 pmu_dmc620_clk_overflow(struct trapframe *tf, struct pmu_dmc620_softc *sc, 205 u_int i) 206 { 207 208 atomic_add_32(&sc->sc_clk_conters_hi[i], 1); 209 /* Call dmc620 handler directly, because hook busy by arm64_intr. */ 210 dmc620_intr(tf, PMC_CLASS_DMC620_PMU_C, sc->sc_unit, i); 211 212 } 213 214 static int 215 pmu_dmc620_counter_overflow_intr(void *arg) 216 { 217 uint32_t clkdiv2_stat, clk_stat; 218 struct pmu_dmc620_softc *sc; 219 struct trapframe *tf; 220 u_int i; 221 222 tf = PCPU_GET(curthread)->td_intr_frame; 223 sc = (struct pmu_dmc620_softc *) arg; 224 clkdiv2_stat = RD4(sc, DMC620_OVERFLOW_STATUS_CLKDIV2); 225 clk_stat = RD4(sc, DMC620_OVERFLOW_STATUS_CLK); 226 227 if ((clkdiv2_stat == 0) && (clk_stat == 0)) 228 return (FILTER_STRAY); 229 /* Stop and save states of all counters. */ 230 for (i = 0; i < DMC620_COUNTERS_N; i++) { 231 sc->sc_saved_control[i] = RD4(sc, DMC620_REG(i, 232 DMC620_COUNTER_CONTROL)); 233 WR4(sc, DMC620_REG(i, DMC620_COUNTER_CONTROL), 234 sc->sc_saved_control[i] & ~DMC620_COUNTER_CONTROL_ENABLE); 235 } 236 237 if (clkdiv2_stat != 0) { 238 for (i = 0; i < DMC620_CLKDIV2_COUNTERS_N; i++) { 239 if ((clkdiv2_stat & (1 << i)) == 0) 240 continue; 241 pmu_dmc620_clkdiv2_overflow(tf, sc, i); 242 } 243 WR4(sc, DMC620_OVERFLOW_STATUS_CLKDIV2, 0); 244 } 245 if (clk_stat != 0) { 246 for (i = 0; i < DMC620_CLK_COUNTERS_N; i++) { 247 if ((clk_stat & (1 << i)) == 0) 248 continue; 249 pmu_dmc620_clk_overflow(tf, sc, i); 250 } 251 WR4(sc, DMC620_OVERFLOW_STATUS_CLK, 0); 252 } 253 254 /* Restore states of all counters. */ 255 for (i = 0; i < DMC620_COUNTERS_N; i++) { 256 WR4(sc, DMC620_REG(i, DMC620_COUNTER_CONTROL), 257 sc->sc_saved_control[i]); 258 } 259 260 return (FILTER_HANDLED); 261 } 262 263 static device_method_t pmu_dmc620_acpi_methods[] = { 264 /* Device interface */ 265 DEVMETHOD(device_probe, pmu_dmc620_acpi_probe), 266 DEVMETHOD(device_attach, pmu_dmc620_acpi_attach), 267 DEVMETHOD(device_detach, pmu_dmc620_acpi_detach), 268 269 /* End */ 270 DEVMETHOD_END 271 }; 272 273 static driver_t pmu_dmc620_acpi_driver = { 274 "pmu_dmc620", 275 pmu_dmc620_acpi_methods, 276 sizeof(struct pmu_dmc620_softc), 277 }; 278 279 DRIVER_MODULE(pmu_dmc620, acpi, pmu_dmc620_acpi_driver, 0, 0); 280 /* Reverse dependency. hwpmc needs DMC-620 on ARM64. */ 281 MODULE_DEPEND(pmc, pmu_dmc620, 1, 1, 1); 282 MODULE_VERSION(pmu_dmc620, 1); 283 #endif /* DEV_ACPI */ 284