xref: /freebsd/sys/arm/mv/mv_thermal.c (revision 90aac0d83bc9645f51ef0c2aeae6f9c0540bb031)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2018 Rubicon Communications, LLC (Netgate)
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 
33 #include <sys/kernel.h>
34 #include <sys/module.h>
35 #include <sys/rman.h>
36 #include <sys/lock.h>
37 #include <sys/mutex.h>
38 #include <sys/sysctl.h>
39 
40 #include <machine/bus.h>
41 #include <machine/resource.h>
42 #include <machine/intr.h>
43 #include <dev/extres/syscon/syscon.h>
44 
45 #include <dev/ofw/ofw_bus.h>
46 #include <dev/ofw/ofw_bus_subr.h>
47 
48 #include "syscon_if.h"
49 
50 #define	CONTROL0		0	/* Offset in config->regs[] array */
51 #define	 CONTROL0_TSEN_START	(1 << 0)
52 #define	 CONTROL0_TSEN_RESET	(1 << 1)
53 #define	 CONTROL0_TSEN_EN	(1 << 2)
54 #define	 CONTROL0_CHANNEL_SHIFT	13
55 #define	 CONTROL0_CHANNEL_MASK	0xF
56 #define	 CONTROL0_OSR_SHIFT	24
57 #define	 CONTROL0_OSR_MAX	3	/* OSR = 512 * 4uS = ~2mS */
58 #define	 CONTROL0_MODE_SHIFT	30
59 #define	 CONTROL0_MODE_EXTERNAL	0x2
60 #define	 CONTROL0_MODE_MASK	0x3
61 
62 #define	CONTROL1		1	/* Offset in config->regs[] array */
63 /* This doesn't seems to work */
64 #define	CONTROL1_TSEN_SENS_SHIFT	21
65 #define	CONTROL1_TSEN_SENS_MASK		0x7
66 
67 #define	STATUS			2	/* Offset in config->regs[] array */
68 #define	STATUS_TEMP_MASK	0x3FF
69 
70 enum mv_thermal_type {
71 	MV_AP806 = 1,
72 	MV_CP110,
73 };
74 
75 struct mv_thermal_config {
76 	enum mv_thermal_type	type;
77 	int			regs[3];
78 	int			ncpus;
79 	int64_t			calib_mul;
80 	int64_t			calib_add;
81 	int64_t			calib_div;
82 	uint32_t		valid_mask;
83 	bool			signed_value;
84 };
85 
86 struct mv_thermal_softc {
87 	device_t		dev;
88 	struct syscon		*syscon;
89 	struct mtx		mtx;
90 
91 	struct mv_thermal_config *config;
92 	int			cur_sensor;
93 };
94 
95 static struct mv_thermal_config mv_ap806_config = {
96 	.type = MV_AP806,
97 	.regs = {0x84, 0x88, 0x8C},
98 	.ncpus = 4,
99 	.calib_mul = 423,
100 	.calib_add = -150000,
101 	.calib_div = 100,
102 	.valid_mask = (1 << 16),
103 	.signed_value = true,
104 };
105 
106 static struct mv_thermal_config mv_cp110_config = {
107 	.type = MV_CP110,
108 	.regs = {0x70, 0x74, 0x78},
109 	.calib_mul = 2000096,
110 	.calib_add = 1172499100,
111 	.calib_div = 420100,
112 	.valid_mask = (1 << 10),
113 	.signed_value = false,
114 };
115 
116 static struct ofw_compat_data compat_data[] = {
117 	{"marvell,armada-ap806-thermal", (uintptr_t) &mv_ap806_config},
118 	{"marvell,armada-cp110-thermal", (uintptr_t) &mv_cp110_config},
119 	{NULL,             0}
120 };
121 
122 #define	RD4(sc, reg)							\
123     SYSCON_READ_4((sc)->syscon, sc->config->regs[reg])
124 #define	WR4(sc, reg, val)						\
125 	SYSCON_WRITE_4((sc)->syscon, sc->config->regs[reg], (val))
126 
127 static inline int32_t sign_extend(uint32_t value, int index)
128 {
129 	uint8_t shift;
130 
131 	shift = 31 - index;
132 	return ((int32_t)(value << shift) >> shift);
133 }
134 
135 static int
136 mv_thermal_wait_sensor(struct mv_thermal_softc *sc)
137 {
138 	uint32_t reg;
139 	uint32_t timeout;
140 
141 	timeout = 100000;
142 	while (--timeout > 0) {
143 		reg = RD4(sc, STATUS);
144 		if ((reg & sc->config->valid_mask) == sc->config->valid_mask)
145 			break;
146 		DELAY(100);
147 	}
148 	if (timeout == 0) {
149 		return (ETIMEDOUT);
150 	}
151 
152 	return (0);
153 }
154 
155 static int
156 mv_thermal_select_sensor(struct mv_thermal_softc *sc, int sensor)
157 {
158 	uint32_t reg;
159 
160 	if (sc->cur_sensor == sensor)
161 		return (0);
162 
163 	/* Stop the current reading and reset the module */
164 	reg = RD4(sc, CONTROL0);
165 	reg &= ~(CONTROL0_TSEN_START | CONTROL0_TSEN_EN);
166 	WR4(sc, CONTROL0, reg);
167 
168 	/* Switch to the selected sensor */
169 	/*
170 	 * NOTE : Datasheet says to use CONTROL1 for selecting
171 	 * but when doing so the sensors >0 are never ready
172 	 * Do what Linux does using undocumented bits in CONTROL0
173 	 */
174 	/* This reset automatically to the sensor 0 */
175 	reg &= ~(CONTROL0_MODE_MASK << CONTROL0_MODE_SHIFT);
176 	if (sensor) {
177 		/* Select external sensor */
178 		reg |= CONTROL0_MODE_EXTERNAL << CONTROL0_MODE_SHIFT;
179 		reg &= ~(CONTROL0_CHANNEL_MASK << CONTROL0_CHANNEL_SHIFT);
180 		reg |= (sensor - 1) << CONTROL0_CHANNEL_SHIFT;
181 	}
182 	WR4(sc, CONTROL0, reg);
183 	sc->cur_sensor = sensor;
184 
185 	/* Start the reading */
186 	reg = RD4(sc, CONTROL0);
187 	reg |= CONTROL0_TSEN_START | CONTROL0_TSEN_EN;
188 	WR4(sc, CONTROL0, reg);
189 
190 	return (mv_thermal_wait_sensor(sc));
191 }
192 
193 static int
194 mv_thermal_read_sensor(struct mv_thermal_softc *sc, int sensor, int *temp)
195 {
196 	uint32_t reg;
197 	int64_t sample, rv;
198 
199 	rv = mv_thermal_select_sensor(sc, sensor);
200 	if (rv != 0)
201 		return (rv);
202 
203 	reg = RD4(sc, STATUS) & STATUS_TEMP_MASK;
204 
205 	if (sc->config->signed_value)
206 		sample = sign_extend(reg, fls(STATUS_TEMP_MASK) - 1);
207 	else
208 		sample = reg;
209 
210 	*temp = ((sample * sc->config->calib_mul) - sc->config->calib_add) /
211 		sc->config->calib_div;
212 
213 	return (0);
214 }
215 
216 static int
217 ap806_init(struct mv_thermal_softc *sc)
218 {
219 	uint32_t reg;
220 
221 	/* Start the temp capture/conversion */
222 	reg = RD4(sc, CONTROL0);
223 	reg &= ~CONTROL0_TSEN_RESET;
224 	reg |= CONTROL0_TSEN_START | CONTROL0_TSEN_EN;
225 
226 	/* Sample every ~2ms */
227 	reg |= CONTROL0_OSR_MAX << CONTROL0_OSR_SHIFT;
228 
229 	WR4(sc, CONTROL0, reg);
230 
231 	/* Since we just started the module wait for the sensor to be ready */
232 	mv_thermal_wait_sensor(sc);
233 
234 	return (0);
235 }
236 
237 static int
238 cp110_init(struct mv_thermal_softc *sc)
239 {
240 	uint32_t reg;
241 
242 	reg = RD4(sc, CONTROL1);
243 	reg &= (1 << 7);
244 	reg |= (1 << 8);
245 	WR4(sc, CONTROL1, reg);
246 
247 	/* Sample every ~2ms */
248 	reg = RD4(sc, CONTROL0);
249 	reg |= CONTROL0_OSR_MAX << CONTROL0_OSR_SHIFT;
250 	WR4(sc, CONTROL0, reg);
251 
252 	return (0);
253 }
254 
255 static int
256 mv_thermal_sysctl(SYSCTL_HANDLER_ARGS)
257 {
258 	struct mv_thermal_softc *sc;
259 	device_t dev = arg1;
260 	int sensor = arg2;
261 	int val = 0;
262 
263 	sc = device_get_softc(dev);
264 	mtx_lock(&(sc)->mtx);
265 
266 	if (mv_thermal_read_sensor(sc, sensor, &val) == 0) {
267 		/* Convert to Kelvin */
268 		val = val + 2732;
269 	} else {
270 		device_printf(dev, "Timeout waiting for sensor\n");
271 	}
272 
273 	mtx_unlock(&(sc)->mtx);
274 	return sysctl_handle_opaque(oidp, &val, sizeof(val), req);
275 }
276 
277 static int
278 mv_thermal_probe(device_t dev)
279 {
280 
281 	if (!ofw_bus_status_okay(dev))
282 		return (ENXIO);
283 
284 	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
285 		return (ENXIO);
286 
287 	device_set_desc(dev, "Marvell Thermal Sensor Controller");
288 	return (BUS_PROBE_DEFAULT);
289 }
290 
291 static int
292 mv_thermal_attach(device_t dev)
293 {
294 	struct mv_thermal_softc *sc;
295 	struct sysctl_ctx_list *ctx;
296 	struct sysctl_oid_list *oid;
297 	char name[255];
298 	char desc[255];
299 	int i;
300 
301 	sc = device_get_softc(dev);
302 	sc->dev = dev;
303 
304 	sc->config = (struct mv_thermal_config *)
305 	    ofw_bus_search_compatible(dev, compat_data)->ocd_data;
306 
307 	mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF);
308 
309 	if (SYSCON_GET_HANDLE(sc->dev, &sc->syscon) != 0 ||
310 	    sc->syscon == NULL) {
311 		device_printf(dev, "cannot get syscon for device\n");
312 		return (ENXIO);
313 	}
314 
315 	sc->cur_sensor = -1;
316 	switch (sc->config->type) {
317 	case MV_AP806:
318 		ap806_init(sc);
319 		break;
320 	case MV_CP110:
321 		cp110_init(sc);
322 		break;
323 	}
324 
325 	ctx = device_get_sysctl_ctx(dev);
326 	oid = SYSCTL_CHILDREN(device_get_sysctl_tree(dev));
327 	/* There is always at least one sensor */
328 	SYSCTL_ADD_PROC(ctx, oid, OID_AUTO, "internal",
329 	    CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_NEEDGIANT,
330 	    dev, 0, mv_thermal_sysctl,
331 	    "IK",
332 	    "Internal Temperature");
333 
334 	for (i = 0; i < sc->config->ncpus; i++) {
335 		snprintf(name, sizeof(name), "cpu%d", i);
336 		snprintf(desc, sizeof(desc), "CPU%d Temperature", i);
337 		SYSCTL_ADD_PROC(ctx, oid, OID_AUTO, name,
338 		    CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_NEEDGIANT,
339 		    dev, i + 1, mv_thermal_sysctl,
340 		    "IK",
341 		    desc);
342 	}
343 
344 	return (0);
345 }
346 
347 static int
348 mv_thermal_detach(device_t dev)
349 {
350 	return (0);
351 }
352 
353 static device_method_t mv_thermal_methods[] = {
354 	/* Device interface */
355 	DEVMETHOD(device_probe,		mv_thermal_probe),
356 	DEVMETHOD(device_attach,	mv_thermal_attach),
357 	DEVMETHOD(device_detach,	mv_thermal_detach),
358 
359 	DEVMETHOD_END
360 };
361 
362 static driver_t mv_thermal_driver = {
363 	"mv_thermal",
364 	mv_thermal_methods,
365 	sizeof(struct mv_thermal_softc),
366 };
367 
368 DRIVER_MODULE(mv_thermal, simplebus, mv_thermal_driver, 0, 0);
369