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