/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2021 Alstom Group. * Copyright (c) 2021 Semihalf. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define BIT(x) (1UL << (x)) /* register map */ #define TMP461_LOCAL_TEMP_REG_MSB 0x0 #define TMP461_LOCAL_TEMP_REG_LSB 0x15 #define TMP461_GLOBAL_TEMP_REG_MSB 0x1 #define TMP461_GLOBAL_TEMP_REG_LSB 0x10 #define TMP461_STATUS_REG 0x2 #define TMP461_STATUS_REG_TEMP_LOCAL BIT(2) #define TMP461_CONFIG_REG_R 0x3 #define TMP461_CONFIG_REG_W 0x9 #define TMP461_CONFIG_REG_TEMP_RANGE_BIT BIT(2) #define TMP461_CONFIG_REG_STANDBY_BIT BIT(6) #define TMP461_CONVERSION_RATE_REG 0x4 #define TMP461_ONESHOT_REG 0xF #define TMP461_EXTENDED_TEMP_MODIFIER 64 /* 28.4 fixed point representation of 273.15f */ #define TMP461_C_TO_K_FIX 4370 #define TMP461_SENSOR_MAX_CONV_TIME 16000000 #define TMP461_LOCAL_MEASURE 0 #define TMP461_REMOTE_MEASURE 1 /* flags */ #define TMP461_LOCAL_TEMP_DOUBLE_REG BIT(0) #define TMP461_REMOTE_TEMP_DOUBLE_REG BIT(1) static int tmp461_probe(device_t dev); static int tmp461_attach(device_t dev); static int tmp461_read_1(device_t dev, uint8_t reg, uint8_t *data); static int tmp461_write_1(device_t dev, uint8_t reg, uint8_t data); static int tmp461_read_temperature(device_t dev, int32_t *temperature, bool mode); static int tmp461_detach(device_t dev); static int tmp461_sensor_sysctl(SYSCTL_HANDLER_ARGS); static device_method_t tmp461_methods[] = { DEVMETHOD(device_probe, tmp461_probe), DEVMETHOD(device_attach, tmp461_attach), DEVMETHOD(device_detach, tmp461_detach), DEVMETHOD_END }; struct tmp461_softc { struct mtx mtx; uint8_t conf; }; static driver_t tmp461_driver = { "tmp461_dev", tmp461_methods, sizeof(struct tmp461_softc) }; struct tmp461_data { const char *compat; const char *desc; uint8_t flags; }; static struct tmp461_data sensor_list[] = { {"adt7461", "ADT7461 Thernal Sensor Information", TMP461_REMOTE_TEMP_DOUBLE_REG}, {"tmp461", "TMP461 Thernal Sensor Information", TMP461_LOCAL_TEMP_DOUBLE_REG | TMP461_REMOTE_TEMP_DOUBLE_REG} }; static struct ofw_compat_data tmp461_compat_data[] = { {"adi,adt7461", (uintptr_t)&sensor_list[0]}, {"ti,tmp461", (uintptr_t)&sensor_list[1]}, {NULL, 0} }; DRIVER_MODULE(tmp461, iicbus, tmp461_driver, 0, 0); IICBUS_FDT_PNP_INFO(tmp461_compat_data); static int tmp461_attach(device_t dev) { struct sysctl_oid *sensor_root_oid; struct tmp461_data *compat_data; struct sysctl_ctx_list *ctx; struct tmp461_softc *sc; uint8_t data; sc = device_get_softc(dev); compat_data = (struct tmp461_data *) ofw_bus_search_compatible(dev, tmp461_compat_data)->ocd_data; sc->conf = compat_data->flags; ctx = device_get_sysctl_ctx(dev); mtx_init(&sc->mtx, "tmp461 temperature", "temperature", MTX_DEF); sensor_root_oid = SYSCTL_ADD_NODE(ctx, SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO, "temperature", CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "Thermal Sensor Information"); if (sensor_root_oid == NULL) return (ENXIO); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(sensor_root_oid), OID_AUTO, "local_sensor", CTLTYPE_INT | CTLFLAG_RD, dev, TMP461_LOCAL_MEASURE, tmp461_sensor_sysctl, "IK1", compat_data->desc); /* get status register */ if (tmp461_read_1(dev, TMP461_STATUS_REG, &data) != 0) return (ENXIO); if (!(data & TMP461_STATUS_REG_TEMP_LOCAL)) SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(sensor_root_oid), OID_AUTO, "remote_sensor", CTLTYPE_INT | CTLFLAG_RD, dev, TMP461_REMOTE_MEASURE, tmp461_sensor_sysctl, "IK1", compat_data->desc); /* set standby mode */ if (tmp461_read_1(dev, TMP461_CONFIG_REG_R, &data) != 0) return (ENXIO); data |= TMP461_CONFIG_REG_STANDBY_BIT; if (tmp461_write_1(dev, TMP461_CONFIG_REG_W, data) != 0) return (ENXIO); return (0); } static int tmp461_probe(device_t dev) { struct tmp461_data *compat_data; if (!ofw_bus_status_okay(dev)) return (ENXIO); compat_data = (struct tmp461_data *) ofw_bus_search_compatible(dev, tmp461_compat_data)->ocd_data; if (!compat_data) return (ENXIO); device_set_desc(dev, compat_data->compat); return (BUS_PROBE_GENERIC); } static int tmp461_detach(device_t dev) { struct tmp461_softc *sc; sc = device_get_softc(dev); mtx_destroy(&sc->mtx); return (0); } static int tmp461_read_1(device_t dev, uint8_t reg, uint8_t *data) { int error; error = iicdev_readfrom(dev, reg, (void *) data, 1, IIC_DONTWAIT); if (error != 0) device_printf(dev, "Failed to read from device\n"); return (error); } static int tmp461_write_1(device_t dev, uint8_t reg, uint8_t data) { int error; error = iicdev_writeto(dev, reg, (void *) &data, 1, IIC_DONTWAIT); if (error != 0) device_printf(dev, "Failed to write to device\n"); return (error); } static int tmp461_read_temperature(device_t dev, int32_t *temperature, bool remote_measure) { uint8_t data, offset, reg; struct tmp461_softc *sc; int error; sc = device_get_softc(dev); mtx_lock(&sc->mtx); error = tmp461_read_1(dev, TMP461_CONVERSION_RATE_REG, &data); if (error != 0) goto fail; /* trigger sample*/ error = tmp461_write_1(dev, TMP461_ONESHOT_REG, 0xFF); if (error != 0) goto fail; /* wait for conversion time */ DELAY(TMP461_SENSOR_MAX_CONV_TIME/(1UL<conf & TMP461_REMOTE_TEMP_DOUBLE_REG) { error = tmp461_read_1(dev, TMP461_GLOBAL_TEMP_REG_LSB, &data); if (error != 0) goto fail; *temperature |= data >> 4; } } else { if (sc->conf & TMP461_LOCAL_TEMP_DOUBLE_REG) { error = tmp461_read_1(dev, TMP461_LOCAL_TEMP_REG_LSB, &data); if (error != 0) goto fail; *temperature |= data >> 4; } } *temperature = (((*temperature + TMP461_C_TO_K_FIX) * 10) >> 4); fail: mtx_unlock(&sc->mtx); return (error); } static int tmp461_sensor_sysctl(SYSCTL_HANDLER_ARGS) { int32_t temperature; device_t dev; int error; bool mode; dev = arg1; mode = arg2; error = tmp461_read_temperature(dev, &temperature, mode); if (error != 0) return (error); return (sysctl_handle_int(oidp, &temperature, 0, req)); }