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