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