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