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