1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright 2020 Michal Meloun <mmel@FreeBSD.org> 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 AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, 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 #include <sys/param.h> 30 #include <sys/systm.h> 31 #include <sys/bus.h> 32 #include <sys/cpu.h> 33 #include <sys/kernel.h> 34 #include <sys/lock.h> 35 #include <sys/malloc.h> 36 #include <sys/module.h> 37 #include <sys/sysctl.h> 38 39 #include <machine/bus.h> 40 #include <machine/cpu.h> 41 42 #include <dev/extres/clk/clk.h> 43 #include <dev/ofw/ofw_bus_subr.h> 44 45 #include "tegra_soctherm_if.h" 46 47 48 enum therm_info { 49 CORETEMP_TEMP, 50 CORETEMP_DELTA, 51 CORETEMP_RESOLUTION, 52 CORETEMP_TJMAX, 53 }; 54 55 struct tegra210_coretemp_softc { 56 device_t dev; 57 int overheat_log; 58 int core_max_temp; 59 int cpu_id; 60 device_t tsens_dev; 61 intptr_t tsens_id; 62 }; 63 64 static int 65 coretemp_get_val_sysctl(SYSCTL_HANDLER_ARGS) 66 { 67 device_t dev; 68 int val, temp, rv; 69 struct tegra210_coretemp_softc *sc; 70 enum therm_info type; 71 char stemp[16]; 72 73 74 dev = (device_t) arg1; 75 sc = device_get_softc(dev); 76 type = arg2; 77 78 79 rv = TEGRA_SOCTHERM_GET_TEMPERATURE(sc->tsens_dev, sc->dev, 80 sc->tsens_id, &temp); 81 if (rv != 0) { 82 device_printf(sc->dev, 83 "Cannot read temperature sensor %u: %d\n", 84 (unsigned int)sc->tsens_id, rv); 85 return (rv); 86 } 87 88 switch (type) { 89 case CORETEMP_TEMP: 90 val = temp / 100; 91 val += 2731; 92 break; 93 case CORETEMP_DELTA: 94 val = (sc->core_max_temp - temp) / 1000; 95 break; 96 case CORETEMP_RESOLUTION: 97 val = 1; 98 break; 99 case CORETEMP_TJMAX: 100 val = sc->core_max_temp / 100; 101 val += 2731; 102 break; 103 } 104 105 106 if ((temp > sc->core_max_temp) && !sc->overheat_log) { 107 sc->overheat_log = 1; 108 109 /* 110 * Check for Critical Temperature Status and Critical 111 * Temperature Log. It doesn't really matter if the 112 * current temperature is invalid because the "Critical 113 * Temperature Log" bit will tell us if the Critical 114 * Temperature has * been reached in past. It's not 115 * directly related to the current temperature. 116 * 117 * If we reach a critical level, allow devctl(4) 118 * to catch this and shutdown the system. 119 */ 120 device_printf(dev, "critical temperature detected, " 121 "suggest system shutdown\n"); 122 snprintf(stemp, sizeof(stemp), "%d", val); 123 devctl_notify("coretemp", "Thermal", stemp, 124 "notify=0xcc"); 125 } else { 126 sc->overheat_log = 0; 127 } 128 129 return (sysctl_handle_int(oidp, 0, val, req)); 130 } 131 132 static int 133 tegra210_coretemp_ofw_parse(struct tegra210_coretemp_softc *sc) 134 { 135 int rv, ncells; 136 phandle_t node, xnode; 137 pcell_t *cells; 138 139 node = OF_peer(0); 140 node = ofw_bus_find_child(node, "thermal-zones"); 141 if (node <= 0) { 142 device_printf(sc->dev, "Cannot find 'thermal-zones'.\n"); 143 return (ENXIO); 144 } 145 146 node = ofw_bus_find_child(node, "cpu"); 147 if (node <= 0) { 148 device_printf(sc->dev, "Cannot find 'cpu'\n"); 149 return (ENXIO); 150 } 151 rv = ofw_bus_parse_xref_list_alloc(node, "thermal-sensors", 152 "#thermal-sensor-cells", 0, &xnode, &ncells, &cells); 153 if (rv != 0) { 154 device_printf(sc->dev, 155 "Cannot parse 'thermal-sensors' property.\n"); 156 return (ENXIO); 157 } 158 if (ncells != 1) { 159 device_printf(sc->dev, 160 "Invalid format of 'thermal-sensors' property(%d).\n", 161 ncells); 162 return (ENXIO); 163 } 164 165 sc->tsens_id = 0x100 + sc->cpu_id; 166 OF_prop_free(cells); 167 168 sc->tsens_dev = OF_device_from_xref(xnode); 169 if (sc->tsens_dev == NULL) { 170 device_printf(sc->dev, 171 "Cannot find thermal sensors device."); 172 return (ENXIO); 173 } 174 return (0); 175 } 176 177 static void 178 tegra210_coretemp_identify(driver_t *driver, device_t parent) 179 { 180 phandle_t root; 181 182 root = OF_finddevice("/"); 183 if (!ofw_bus_node_is_compatible(root, "nvidia,tegra210")) 184 return; 185 if (device_find_child(parent, "tegra210_coretemp", -1) != NULL) 186 return; 187 if (BUS_ADD_CHILD(parent, 0, "tegra210_coretemp", -1) == NULL) 188 device_printf(parent, "add child failed\n"); 189 } 190 191 static int 192 tegra210_coretemp_probe(device_t dev) 193 { 194 195 device_set_desc(dev, "CPU Thermal Sensor"); 196 return (0); 197 } 198 199 static int 200 tegra210_coretemp_attach(device_t dev) 201 { 202 struct tegra210_coretemp_softc *sc; 203 device_t pdev; 204 struct sysctl_oid *oid; 205 struct sysctl_ctx_list *ctx; 206 int rv; 207 208 sc = device_get_softc(dev); 209 sc->dev = dev; 210 sc->cpu_id = device_get_unit(dev); 211 sc->core_max_temp = 102000; 212 pdev = device_get_parent(dev); 213 214 rv = tegra210_coretemp_ofw_parse(sc); 215 if (rv != 0) 216 return (rv); 217 218 ctx = device_get_sysctl_ctx(dev); 219 220 oid = SYSCTL_ADD_NODE(ctx, 221 SYSCTL_CHILDREN(device_get_sysctl_tree(pdev)), OID_AUTO, 222 "coretemp", CTLFLAG_RD, NULL, "Per-CPU thermal information"); 223 224 /* 225 * Add the MIBs to dev.cpu.N and dev.cpu.N.coretemp. 226 */ 227 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(pdev)), 228 OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, 229 dev, CORETEMP_TEMP, coretemp_get_val_sysctl, "IK", 230 "Current temperature"); 231 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "delta", 232 CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORETEMP_DELTA, 233 coretemp_get_val_sysctl, "I", 234 "Delta between TCC activation and current temperature"); 235 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "resolution", 236 CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORETEMP_RESOLUTION, 237 coretemp_get_val_sysctl, "I", 238 "Resolution of CPU thermal sensor"); 239 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "tjmax", 240 CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORETEMP_TJMAX, 241 coretemp_get_val_sysctl, "IK", 242 "TCC activation temperature"); 243 244 return (0); 245 } 246 247 static int 248 tegra210_coretemp_detach(device_t dev) 249 { 250 return (0); 251 } 252 253 static device_method_t tegra210_coretemp_methods[] = { 254 /* Device interface */ 255 DEVMETHOD(device_identify, tegra210_coretemp_identify), 256 DEVMETHOD(device_probe, tegra210_coretemp_probe), 257 DEVMETHOD(device_attach, tegra210_coretemp_attach), 258 DEVMETHOD(device_detach, tegra210_coretemp_detach), 259 260 DEVMETHOD_END 261 }; 262 263 static DEFINE_CLASS_0(tegra210_coretemp, tegra210_coretemp_driver, 264 tegra210_coretemp_methods, sizeof(struct tegra210_coretemp_softc)); 265 DRIVER_MODULE(tegra210_coretemp, cpu, tegra210_coretemp_driver, NULL, NULL); 266