nxp75a.c revision 1.1 1 /* $NetBSD: nxp75a.c,v 1.1 2026/06/03 11:08:41 jdc Exp $ */
2
3 /*-
4 * Copyright (c) 2026 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Julian Coleman.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #include <sys/cdefs.h>
33 __KERNEL_RCSID(0, "$NetBSD: nxp75a.c,v 1.1 2026/06/03 11:08:41 jdc Exp $");
34
35 #include <sys/param.h>
36 #include <sys/systm.h>
37 #include <sys/device.h>
38 #include <sys/kernel.h>
39
40 #include <dev/sysmon/sysmonvar.h>
41
42 #include <dev/i2c/i2cvar.h>
43 #include <dev/i2c/nxp75areg.h>
44
45 struct nxp75a_softc {
46 device_t sc_dev;
47 i2c_tag_t sc_tag;
48 int sc_address;
49
50 struct sysmon_envsys *sc_sme;
51 envsys_data_t sc_sensor;
52 uint32_t sc_otemp, sc_hyst;
53 };
54
55 static int nxp75a_match(device_t, cfdata_t, void *);
56 static void nxp75a_attach(device_t, device_t, void *);
57 static int nxp75a_detach(device_t, int);
58 void nxp75a_refresh(struct sysmon_envsys *, envsys_data_t *);
59 void nxp75a_get_limits(struct sysmon_envsys *, envsys_data_t *,
60 sysmon_envsys_lim_t *, uint32_t *);
61 void nxp75a_set_limits(struct sysmon_envsys *, envsys_data_t *,
62 sysmon_envsys_lim_t *, uint32_t *);
63 static int nxp75a_read_temp(struct nxp75a_softc *, uint8_t, uint32_t *);
64 static int nxp75a_write_temp(struct nxp75a_softc *, uint8_t, uint32_t);
65
66 CFATTACH_DECL_NEW(nxp75a, sizeof(struct nxp75a_softc),
67 nxp75a_match, nxp75a_attach, nxp75a_detach, NULL);
68
69
70 static const struct device_compatible_entry compat_data[] = {
71 { .compat = "i2c-lm75a" },
72 DEVICE_COMPAT_EOL
73 };
74
75 static int
76 nxp75a_match(device_t parent, cfdata_t cf, void *aux)
77 {
78 struct i2c_attach_args *ia = aux;
79 int match_result;
80
81 if (iic_use_direct_match(ia, cf, compat_data, &match_result))
82 return match_result;
83
84 /*
85 * Indirect config - assume config is correct!
86 */
87 if ((ia->ia_addr & NXP75A_ADDRMASK) == NXP75A_ADDR)
88 return I2C_MATCH_ADDRESS_ONLY;
89
90 return 0;
91 }
92
93 static void
94 nxp75a_attach(device_t parent, device_t self, void *aux)
95 {
96 struct nxp75a_softc *sc = device_private(self);
97 struct i2c_attach_args *ia = aux;
98 uint8_t reg, val;
99 uint32_t tval;
100
101 sc->sc_tag = ia->ia_tag;
102 sc->sc_address = ia->ia_addr;
103 sc->sc_dev = self;
104
105 aprint_normal(": NXP LM75A temperature sensor\n");
106
107 if (iic_acquire_bus(sc->sc_tag, 0)) {
108 aprint_error_dev(self,
109 "unable to acquire I2C bus\n");
110 return;
111 }
112
113 /* Read and save overtemp (critical limit) and hysteresis */
114 reg = NXP75A_OVERTEMP;
115 if (nxp75a_read_temp(sc, reg, &tval)) {
116 iic_release_bus(sc->sc_tag, 0);
117 aprint_error_dev(sc->sc_dev,
118 "unable to read overtemp register\n");
119 return;
120 }
121 sc->sc_otemp = tval;
122 reg = NXP75A_THYST;
123 if (nxp75a_read_temp(sc, reg, &tval)) {
124 iic_release_bus(sc->sc_tag, 0);
125 aprint_error_dev(sc->sc_dev,
126 "unable to read hysteresis register\n");
127 return;
128 }
129 sc->sc_hyst = tval;
130
131 /* Read config, start conversion */
132 reg = NXP75A_CONFIG;
133 if (iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
134 sc->sc_address, ®, 1, &val, 1, 0)) {
135 iic_release_bus(sc->sc_tag, 0);
136 aprint_error_dev(sc->sc_dev,
137 "unable to read config register\n");
138 return;
139 }
140
141 if (val & NXP75A_CONF_SHUTDOWN) {
142 val &= ~(NXP75A_CONF_SHUTDOWN);
143 if (iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
144 sc->sc_address, ®, 1, &val, 1, 0)) {
145 iic_release_bus(sc->sc_tag, 0);
146 aprint_error_dev(sc->sc_dev,
147 "unable to write config register\n");
148 return;
149 }
150 }
151
152 iic_release_bus(sc->sc_tag, 0);
153
154 sc->sc_sme = sysmon_envsys_create();
155 sc->sc_sme->sme_name = device_xname(self);
156 sc->sc_sme->sme_cookie = sc;
157 sc->sc_sme->sme_refresh = nxp75a_refresh;
158 sc->sc_sme->sme_get_limits = nxp75a_get_limits;
159 sc->sc_sme->sme_set_limits = nxp75a_set_limits;
160
161 strlcpy(sc->sc_sensor.desc, "temperature", sizeof(sc->sc_sensor.desc));
162 sc->sc_sensor.units = ENVSYS_STEMP;
163 sc->sc_sensor.state = ENVSYS_SINVALID;
164 sc->sc_sensor.flags = ENVSYS_FMONLIMITS | ENVSYS_FHAS_ENTROPY;
165
166 if (sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor)) {
167 sysmon_envsys_destroy(sc->sc_sme);
168 sc->sc_sme = NULL;
169 aprint_error_dev(sc->sc_dev,
170 "unable to attach sensor to sysmon\n");
171 return;
172 }
173
174 if (sysmon_envsys_register(sc->sc_sme)) {
175 aprint_error_dev(self, "unable to register with sysmon\n");
176 sysmon_envsys_destroy(sc->sc_sme);
177 sc->sc_sme = NULL;
178 return;
179 }
180 }
181
182 static int
183 nxp75a_detach(device_t self, int flags)
184 {
185 struct nxp75a_softc *sc = device_private(self);
186
187 if (sc->sc_sme != NULL) {
188 sysmon_envsys_unregister(sc->sc_sme);
189 sc->sc_sme = NULL;
190 }
191
192 return 0;
193 }
194
195 void
196 nxp75a_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
197 {
198 struct nxp75a_softc *sc = sme->sme_cookie;
199 uint8_t reg;
200 uint32_t val;
201
202 if (iic_acquire_bus(sc->sc_tag, 0))
203 return;
204
205 reg = NXP75A_TEMP;
206 if (nxp75a_read_temp(sc, reg, &val) == 0) {
207 edata->value_cur = val;
208 edata->state = ENVSYS_SVALID;
209 } else {
210 edata->state = ENVSYS_SINVALID;
211 }
212
213 iic_release_bus(sc->sc_tag, 0);
214 }
215
216 void
217 nxp75a_get_limits(struct sysmon_envsys *sme, envsys_data_t *edata,
218 sysmon_envsys_lim_t *limits, uint32_t *props)
219 {
220 struct nxp75a_softc *sc = sme->sme_cookie;
221 uint8_t reg;
222 uint32_t val;
223
224 *props &= ~(PROP_CRITMAX | PROP_WARNMAX | PROP_WARNMIN);
225
226 if (iic_acquire_bus(sc->sc_tag, 0))
227 return;
228
229 reg = NXP75A_OVERTEMP;
230 if (nxp75a_read_temp(sc, reg, &val) == 0) {
231 limits->sel_critmax = val;
232 *props |= PROP_CRITMAX;
233 }
234
235 iic_release_bus(sc->sc_tag, 0);
236 }
237
238 void
239 nxp75a_set_limits(struct sysmon_envsys *sme, envsys_data_t *edata,
240 sysmon_envsys_lim_t *limits, uint32_t *props)
241 {
242 struct nxp75a_softc *sc = sme->sme_cookie;
243 uint8_t reg;
244 uint32_t otemp, hyst;
245
246 if (iic_acquire_bus(sc->sc_tag, 0))
247 return;
248
249 if (limits == NULL || *props & PROP_CRITMAX) {
250 if (limits == NULL) { /* Restore defaults */
251 otemp = sc->sc_otemp;
252 hyst = sc->sc_hyst;
253 } else {
254 otemp = limits->sel_critmax;
255 /* Make sure that hyst is less than overtemp */
256 hyst = otemp - (sc->sc_otemp - sc->sc_hyst);
257 }
258 reg = NXP75A_OVERTEMP;
259 if (nxp75a_write_temp(sc, reg, otemp) == 0) {
260 reg = NXP75A_THYST;
261 nxp75a_write_temp(sc, reg, hyst);
262 }
263 }
264
265 iic_release_bus(sc->sc_tag, 0);
266 }
267
268 static int
269 nxp75a_read_temp(struct nxp75a_softc *sc, uint8_t reg, uint32_t *valp)
270 {
271 uint8_t buf[NXP75A_TEMP_LEN];
272 int err;
273
274 err = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
275 sc->sc_address, ®, 1, buf, NXP75A_TEMP_LEN, 0);
276 if (err)
277 return err;
278
279 /*
280 * Convert the 11 high bits from the 2 bytes to a temperature in uK.
281 * Sign-extend the MSB and add in the LSB
282 */
283 *valp = (int8_t) buf[0];
284 *valp = (*valp << 3) + ((buf[1] >> 5) & 0x07);
285 *valp = *valp * 125000 + 273150000;
286
287 return 0;
288 }
289
290 static int
291 nxp75a_write_temp(struct nxp75a_softc *sc, uint8_t reg, uint32_t val)
292 {
293 uint8_t buf[NXP75A_TEMP_LEN];
294 uint32_t temp;
295
296 /* Convert the temperature in uK to 11 high bits in 2 bytes.*/
297 temp = (int) (val - 273150000) / 125000 ;
298 buf[0] = (temp >> 3) & 0xff;
299 buf[1] = (temp << 5) & 0xe0;
300
301 return iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
302 sc->sc_address, ®, 1, buf, NXP75A_TEMP_LEN, 0);
303 }
304
305