xref: /freebsd/sys/dev/iicbus/sensor/tmp461.c (revision 61e21613)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2021 Alstom Group.
5  * Copyright (c) 2021 Semihalf.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include <sys/cdefs.h>
29 #include "opt_platform.h"
30 
31 #include <sys/param.h>
32 #include <sys/bus.h>
33 #include <sys/lock.h>
34 #include <sys/mutex.h>
35 #include <sys/module.h>
36 #include <sys/ctype.h>
37 #include <sys/kernel.h>
38 #include <sys/libkern.h>
39 #include <sys/sysctl.h>
40 
41 #include <dev/iicbus/iicbus.h>
42 #include <dev/iicbus/iiconf.h>
43 
44 #include <dev/ofw/ofw_bus_subr.h>
45 #include <dev/ofw/ofw_bus.h>
46 
47 #define BIT(x)					(1UL << (x))
48 
49 /* register map */
50 #define TMP461_LOCAL_TEMP_REG_MSB		0x0
51 #define TMP461_LOCAL_TEMP_REG_LSB		0x15
52 #define TMP461_GLOBAL_TEMP_REG_MSB		0x1
53 #define TMP461_GLOBAL_TEMP_REG_LSB		0x10
54 #define TMP461_STATUS_REG			0x2
55 #define TMP461_STATUS_REG_TEMP_LOCAL		BIT(2)
56 #define TMP461_CONFIG_REG_R			0x3
57 #define TMP461_CONFIG_REG_W			0x9
58 #define TMP461_CONFIG_REG_TEMP_RANGE_BIT	BIT(2)
59 #define TMP461_CONFIG_REG_STANDBY_BIT		BIT(6)
60 #define TMP461_CONVERSION_RATE_REG		0x4
61 #define TMP461_ONESHOT_REG			0xF
62 #define TMP461_EXTENDED_TEMP_MODIFIER		64
63 
64 /* 28.4 fixed point representation of 273.15f */
65 #define TMP461_C_TO_K_FIX			4370
66 
67 #define TMP461_SENSOR_MAX_CONV_TIME		16000000
68 #define TMP461_LOCAL_MEASURE			0
69 #define TMP461_REMOTE_MEASURE			1
70 
71 /* flags */
72 #define TMP461_LOCAL_TEMP_DOUBLE_REG		BIT(0)
73 #define TMP461_REMOTE_TEMP_DOUBLE_REG		BIT(1)
74 
75 static int tmp461_probe(device_t dev);
76 static int tmp461_attach(device_t dev);
77 static int tmp461_read_1(device_t dev, uint8_t reg, uint8_t *data);
78 static int tmp461_write_1(device_t dev, uint8_t reg, uint8_t data);
79 static int tmp461_read_temperature(device_t dev, int32_t *temperature, bool mode);
80 static int tmp461_detach(device_t dev);
81 static int tmp461_sensor_sysctl(SYSCTL_HANDLER_ARGS);
82 
83 static device_method_t tmp461_methods[] = {
84 	DEVMETHOD(device_probe,		tmp461_probe),
85 	DEVMETHOD(device_attach,	tmp461_attach),
86 	DEVMETHOD(device_detach,	tmp461_detach),
87 
88 	DEVMETHOD_END
89 };
90 
91 struct tmp461_softc {
92 	struct mtx		mtx;
93 	uint8_t			conf;
94 };
95 
96 static driver_t tmp461_driver = {
97 	"tmp461_dev",
98 	tmp461_methods,
99 	sizeof(struct tmp461_softc)
100 };
101 
102 struct tmp461_data {
103 	const char	*compat;
104 	const char	*desc;
105 	uint8_t		flags;
106 };
107 
108 static struct tmp461_data sensor_list[] = {
109 	{"adt7461", "ADT7461 Thernal Sensor Information",
110 	    TMP461_REMOTE_TEMP_DOUBLE_REG},
111 	{"tmp461", "TMP461 Thernal Sensor Information",
112 	    TMP461_LOCAL_TEMP_DOUBLE_REG | TMP461_REMOTE_TEMP_DOUBLE_REG}
113 };
114 
115 static struct ofw_compat_data tmp461_compat_data[] = {
116 	{"adi,adt7461",		(uintptr_t)&sensor_list[0]},
117 	{"ti,tmp461",		(uintptr_t)&sensor_list[1]},
118 	{NULL,			0}
119 };
120 
121 DRIVER_MODULE(tmp461, iicbus, tmp461_driver, 0, 0);
122 IICBUS_FDT_PNP_INFO(tmp461_compat_data);
123 
124 static int
125 tmp461_attach(device_t dev)
126 {
127 	struct sysctl_oid *sensor_root_oid;
128 	struct tmp461_data *compat_data;
129 	struct sysctl_ctx_list *ctx;
130 	struct tmp461_softc *sc;
131 	uint8_t data;
132 
133 	sc = device_get_softc(dev);
134 	compat_data = (struct tmp461_data *)
135 	    ofw_bus_search_compatible(dev, tmp461_compat_data)->ocd_data;
136 	sc->conf = compat_data->flags;
137 	ctx = device_get_sysctl_ctx(dev);
138 
139 	mtx_init(&sc->mtx, "tmp461 temperature", "temperature", MTX_DEF);
140 
141 	sensor_root_oid = SYSCTL_ADD_NODE(ctx, SYSCTL_STATIC_CHILDREN(_hw),
142 	    OID_AUTO, "temperature", CTLFLAG_RD | CTLFLAG_MPSAFE, NULL,
143 	    "Thermal Sensor Information");
144 	if (sensor_root_oid == NULL)
145 		return (ENXIO);
146 
147 	SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(sensor_root_oid), OID_AUTO,
148 	    "local_sensor", CTLTYPE_INT | CTLFLAG_RD, dev,
149 	    TMP461_LOCAL_MEASURE, tmp461_sensor_sysctl,
150 	    "IK1", compat_data->desc);
151 
152 	/* get status register */
153 	if (tmp461_read_1(dev, TMP461_STATUS_REG, &data) != 0)
154 		return (ENXIO);
155 
156 	if (!(data & TMP461_STATUS_REG_TEMP_LOCAL))
157 		SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(sensor_root_oid), OID_AUTO,
158 		    "remote_sensor", CTLTYPE_INT | CTLFLAG_RD, dev,
159 		    TMP461_REMOTE_MEASURE, tmp461_sensor_sysctl,
160 		    "IK1", compat_data->desc);
161 
162 	/* set standby mode */
163 	if (tmp461_read_1(dev, TMP461_CONFIG_REG_R, &data) != 0)
164 		return (ENXIO);
165 
166 	data |= TMP461_CONFIG_REG_STANDBY_BIT;
167 	if (tmp461_write_1(dev, TMP461_CONFIG_REG_W, data) != 0)
168 		return (ENXIO);
169 
170 	return (0);
171 }
172 
173 static int
174 tmp461_probe(device_t dev)
175 {
176 	struct tmp461_data *compat_data;
177 
178 	if (!ofw_bus_status_okay(dev))
179 		return (ENXIO);
180 
181 	compat_data = (struct tmp461_data *)
182 	    ofw_bus_search_compatible(dev, tmp461_compat_data)->ocd_data;
183 	if (!compat_data)
184 		return (ENXIO);
185 
186 	device_set_desc(dev, compat_data->compat);
187 
188 	return (BUS_PROBE_GENERIC);
189 }
190 
191 static int
192 tmp461_detach(device_t dev)
193 {
194 	struct tmp461_softc *sc;
195 
196 	sc = device_get_softc(dev);
197 	mtx_destroy(&sc->mtx);
198 
199 	return (0);
200 }
201 
202 static int
203 tmp461_read_1(device_t dev, uint8_t reg, uint8_t *data)
204 {
205 	int error;
206 
207 	error = iicdev_readfrom(dev, reg, (void *) data, 1, IIC_DONTWAIT);
208 	if (error != 0)
209 		device_printf(dev, "Failed to read from device\n");
210 
211 	return (error);
212 }
213 
214 static int
215 tmp461_write_1(device_t dev, uint8_t reg, uint8_t data)
216 {
217 	int error;
218 
219 	error = iicdev_writeto(dev, reg, (void *) &data, 1, IIC_DONTWAIT);
220 	if (error != 0)
221 		device_printf(dev, "Failed to write to device\n");
222 
223 	return (error);
224 }
225 
226 static int
227 tmp461_read_temperature(device_t dev, int32_t *temperature, bool remote_measure)
228 {
229 	uint8_t data, offset, reg;
230 	struct tmp461_softc *sc;
231 	int error;
232 
233 	sc = device_get_softc(dev);
234 
235 	mtx_lock(&sc->mtx);
236 
237 	error = tmp461_read_1(dev, TMP461_CONVERSION_RATE_REG, &data);
238 	if (error != 0)
239 		goto fail;
240 
241 	/* trigger sample*/
242 	error = tmp461_write_1(dev, TMP461_ONESHOT_REG, 0xFF);
243 	if (error != 0)
244 		goto fail;
245 
246 	/* wait for conversion time */
247 	DELAY(TMP461_SENSOR_MAX_CONV_TIME/(1UL<<data));
248 
249 	/* read config register offset */
250 	error = tmp461_read_1(dev, TMP461_CONFIG_REG_R, &data);
251 	if (error != 0)
252 		goto fail;
253 
254 	offset = (data & TMP461_CONFIG_REG_TEMP_RANGE_BIT ?
255 	    TMP461_EXTENDED_TEMP_MODIFIER : 0);
256 
257 	reg = remote_measure ?
258 	    TMP461_GLOBAL_TEMP_REG_MSB : TMP461_LOCAL_TEMP_REG_MSB;
259 
260 	/* read temeperature value*/
261 	error = tmp461_read_1(dev, reg, &data);
262 	if (error != 0)
263 		goto fail;
264 
265 	data -= offset;
266 	*temperature = signed_extend32(data, 0, 8) << 4;
267 
268 	if (remote_measure) {
269 		if (sc->conf & TMP461_REMOTE_TEMP_DOUBLE_REG) {
270 			error = tmp461_read_1(dev,
271 			    TMP461_GLOBAL_TEMP_REG_LSB, &data);
272 			if (error != 0)
273 				goto fail;
274 
275 			*temperature |= data >> 4;
276 		}
277 	} else {
278 		if (sc->conf & TMP461_LOCAL_TEMP_DOUBLE_REG) {
279 			error = tmp461_read_1(dev,
280 			    TMP461_LOCAL_TEMP_REG_LSB, &data);
281 			if (error != 0)
282 				goto fail;
283 
284 			*temperature |= data >> 4;
285 		}
286 	}
287 	*temperature = (((*temperature + TMP461_C_TO_K_FIX) * 10) >> 4);
288 
289 fail:
290 	mtx_unlock(&sc->mtx);
291 	return (error);
292 }
293 
294 static int
295 tmp461_sensor_sysctl(SYSCTL_HANDLER_ARGS)
296 {
297 	int32_t temperature;
298 	device_t dev;
299 	int error;
300 	bool mode;
301 
302 	dev = arg1;
303 	mode = arg2;
304 
305 	error = tmp461_read_temperature(dev, &temperature, mode);
306 	if (error != 0)
307 		return (error);
308 
309 	return (sysctl_handle_int(oidp, &temperature, 0, req));
310 }
311