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 28 #include <sys/cdefs.h> 29 __FBSDID("$FreeBSD$"); 30 31 #include "opt_hwpmc_hooks.h" 32 #include "opt_acpi.h" 33 34 #include <sys/param.h> 35 #include <sys/bus.h> 36 #include <sys/module.h> 37 #include <sys/rman.h> 38 #include <sys/pmc.h> 39 #include <sys/pmckern.h> 40 41 #include <machine/bus.h> 42 #include <machine/cpu.h> 43 44 #include <contrib/dev/acpica/include/acpi.h> 45 #include <dev/acpica/acpivar.h> 46 47 #include <dev/hwpmc/pmu_dmc620_reg.h> 48 49 static char *pmu_dmc620_ids[] = { 50 "ARMHD620", 51 NULL 52 }; 53 54 static struct resource_spec pmu_dmc620_res_spec[] = { 55 { SYS_RES_MEMORY, 0, RF_ACTIVE }, 56 { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE }, 57 { -1, 0 } 58 }; 59 60 struct pmu_dmc620_softc { 61 device_t sc_dev; 62 int sc_unit; 63 int sc_domain; 64 struct resource *sc_res[2]; 65 void *sc_ih; 66 uint32_t sc_clkdiv2_conters_hi[DMC620_CLKDIV2_COUNTERS_N]; 67 uint32_t sc_clk_conters_hi[DMC620_CLK_COUNTERS_N]; 68 uint32_t sc_saved_control[DMC620_COUNTERS_N]; 69 }; 70 71 #define RD4(sc, r) bus_read_4((sc)->sc_res[0], (r)) 72 #define WR4(sc, r, v) bus_write_4((sc)->sc_res[0], (r), (v)) 73 #define MD4(sc, r, c, s) WR4((sc), (r), RD4((sc), (r)) & ~(c) | (s)) 74 75 #define CD2MD4(sc, u, r, c, s) MD4((sc), DMC620_CLKDIV2_REG((u), (r)), (c), (s)) 76 #define CMD4(sc, u, r, c, s) MD4((sc), DMC620_CLK_REG((u), (r)), (c), (s)) 77 78 static int pmu_dmc620_counter_overflow_intr(void *arg); 79 80 uint32_t 81 pmu_dmc620_rd4(void *arg, u_int cntr, off_t reg) 82 { 83 struct pmu_dmc620_softc *sc; 84 uint32_t val; 85 86 sc = (struct pmu_dmc620_softc *)arg; 87 KASSERT(cntr < DMC620_COUNTERS_N, ("Wrong counter unit %d", cntr)); 88 89 val = RD4(sc, DMC620_REG(cntr, reg)); 90 return (val); 91 } 92 93 void 94 pmu_dmc620_wr4(void *arg, u_int cntr, off_t reg, uint32_t val) 95 { 96 struct pmu_dmc620_softc *sc; 97 98 sc = (struct pmu_dmc620_softc *)arg; 99 KASSERT(cntr < DMC620_COUNTERS_N, ("Wrong counter unit %d", cntr)); 100 101 WR4(sc, DMC620_REG(cntr, reg), val); 102 } 103 104 static int 105 pmu_dmc620_acpi_probe(device_t dev) 106 { 107 int err; 108 109 err = ACPI_ID_PROBE(device_get_parent(dev), dev, pmu_dmc620_ids, NULL); 110 if (err <= 0) 111 device_set_desc(dev, "ARM DMC-620 Memory Controller PMU"); 112 113 return (err); 114 } 115 116 static int 117 pmu_dmc620_acpi_attach(device_t dev) 118 { 119 struct pmu_dmc620_softc *sc; 120 int domain, i, u; 121 const char *dname; 122 123 dname = device_get_name(dev); 124 sc = device_get_softc(dev); 125 sc->sc_dev = dev; 126 u = device_get_unit(dev); 127 sc->sc_unit = u; 128 129 /* 130 * Ampere Altra support NUMA emulation, but DMC-620 PMU units have no 131 * mapping. Emulate this with kenv/hints. 132 * Format "hint.pmu_dmc620.3.domain=1". 133 */ 134 if ((resource_int_value(dname, u, "domain", &domain) == 0 || 135 bus_get_domain(dev, &domain) == 0) && domain < MAXMEMDOM) { 136 sc->sc_domain = domain; 137 } 138 device_printf(dev, "domain=%d\n", domain); 139 140 i = bus_alloc_resources(dev, pmu_dmc620_res_spec, sc->sc_res); 141 if (i != 0) { 142 device_printf(dev, "cannot allocate resources for device (%d)\n", 143 i); 144 return (i); 145 } 146 /* Disable counter before enable interrupt. */ 147 for (i = 0; i < DMC620_CLKDIV2_COUNTERS_N; i++) { 148 CD2MD4(sc, i, DMC620_COUNTER_CONTROL, 149 DMC620_COUNTER_CONTROL_ENABLE, 0); 150 } 151 for (i = 0; i < DMC620_CLK_COUNTERS_N; i++) { 152 CMD4(sc, i, DMC620_COUNTER_CONTROL, 153 DMC620_COUNTER_CONTROL_ENABLE, 0); 154 } 155 156 /* Clear intr status. */ 157 WR4(sc, DMC620_OVERFLOW_STATUS_CLKDIV2, 0); 158 WR4(sc, DMC620_OVERFLOW_STATUS_CLK, 0); 159 160 if (sc->sc_res[1] != NULL && bus_setup_intr(dev, sc->sc_res[1], 161 INTR_TYPE_MISC | INTR_MPSAFE, pmu_dmc620_counter_overflow_intr, 162 NULL, sc, &sc->sc_ih)) { 163 bus_release_resources(dev, pmu_dmc620_res_spec, sc->sc_res); 164 device_printf(dev, "cannot setup interrupt handler\n"); 165 return (ENXIO); 166 } 167 dmc620_pmc_register(u, sc, domain); 168 return (0); 169 } 170 171 static int 172 pmu_dmc620_acpi_detach(device_t dev) 173 { 174 struct pmu_dmc620_softc *sc; 175 176 sc = device_get_softc(dev); 177 dmc620_pmc_unregister(device_get_unit(dev)); 178 if (sc->sc_res[1] != NULL) { 179 bus_teardown_intr(dev, sc->sc_res[1], sc->sc_ih); 180 } 181 bus_release_resources(dev, pmu_dmc620_res_spec, sc->sc_res); 182 183 return (0); 184 } 185 186 static void 187 pmu_dmc620_clkdiv2_overflow(struct trapframe *tf, struct pmu_dmc620_softc *sc, 188 u_int i) 189 { 190 191 atomic_add_32(&sc->sc_clkdiv2_conters_hi[i], 1); 192 /* Call dmc620 handler directly, because hook busy by arm64_intr. */ 193 dmc620_intr(tf, PMC_CLASS_DMC620_PMU_CD2, sc->sc_unit, i); 194 } 195 196 static void 197 pmu_dmc620_clk_overflow(struct trapframe *tf, struct pmu_dmc620_softc *sc, 198 u_int i) 199 { 200 201 atomic_add_32(&sc->sc_clk_conters_hi[i], 1); 202 /* Call dmc620 handler directly, because hook busy by arm64_intr. */ 203 dmc620_intr(tf, PMC_CLASS_DMC620_PMU_C, sc->sc_unit, i); 204 205 } 206 207 static int 208 pmu_dmc620_counter_overflow_intr(void *arg) 209 { 210 uint32_t clkdiv2_stat, clk_stat; 211 struct pmu_dmc620_softc *sc; 212 struct trapframe *tf; 213 u_int i; 214 215 tf = PCPU_GET(curthread)->td_intr_frame; 216 sc = (struct pmu_dmc620_softc *) arg; 217 clkdiv2_stat = RD4(sc, DMC620_OVERFLOW_STATUS_CLKDIV2); 218 clk_stat = RD4(sc, DMC620_OVERFLOW_STATUS_CLK); 219 220 if ((clkdiv2_stat == 0) && (clk_stat == 0)) 221 return (FILTER_STRAY); 222 /* Stop and save states of all counters. */ 223 for (i = 0; i < DMC620_COUNTERS_N; i++) { 224 sc->sc_saved_control[i] = RD4(sc, DMC620_REG(i, 225 DMC620_COUNTER_CONTROL)); 226 WR4(sc, DMC620_REG(i, DMC620_COUNTER_CONTROL), 227 sc->sc_saved_control[i] & ~DMC620_COUNTER_CONTROL_ENABLE); 228 } 229 230 if (clkdiv2_stat != 0) { 231 for (i = 0; i < DMC620_CLKDIV2_COUNTERS_N; i++) { 232 if ((clkdiv2_stat & (1 << i)) == 0) 233 continue; 234 pmu_dmc620_clkdiv2_overflow(tf, sc, i); 235 } 236 WR4(sc, DMC620_OVERFLOW_STATUS_CLKDIV2, 0); 237 } 238 if (clk_stat != 0) { 239 for (i = 0; i < DMC620_CLK_COUNTERS_N; i++) { 240 if ((clk_stat & (1 << i)) == 0) 241 continue; 242 pmu_dmc620_clk_overflow(tf, sc, i); 243 } 244 WR4(sc, DMC620_OVERFLOW_STATUS_CLK, 0); 245 } 246 247 /* Restore states of all counters. */ 248 for (i = 0; i < DMC620_COUNTERS_N; i++) { 249 WR4(sc, DMC620_REG(i, DMC620_COUNTER_CONTROL), 250 sc->sc_saved_control[i]); 251 } 252 253 return (FILTER_HANDLED); 254 } 255 256 static device_method_t pmu_dmc620_acpi_methods[] = { 257 /* Device interface */ 258 DEVMETHOD(device_probe, pmu_dmc620_acpi_probe), 259 DEVMETHOD(device_attach, pmu_dmc620_acpi_attach), 260 DEVMETHOD(device_detach, pmu_dmc620_acpi_detach), 261 262 /* End */ 263 DEVMETHOD_END 264 }; 265 266 static driver_t pmu_dmc620_acpi_driver = { 267 "pmu_dmc620", 268 pmu_dmc620_acpi_methods, 269 sizeof(struct pmu_dmc620_softc), 270 }; 271 272 DRIVER_MODULE(pmu_dmc620, acpi, pmu_dmc620_acpi_driver, 0, 0); 273 /* Reverse dependency. hwpmc needs DMC-620 on ARM64. */ 274 MODULE_DEPEND(pmc, pmu_dmc620, 1, 1, 1); 275 MODULE_VERSION(pmu_dmc620, 1); 276