diff mbox series

hw/sensors: Add temperature SI705x sensor

Message ID 20250205145704.67791-1-i.chichkov@yadro.com (mailing list archive)
State New
Headers show
Series hw/sensors: Add temperature SI705x sensor | expand

Commit Message

Ilya Chichkov Feb. 5, 2025, 2:57 p.m. UTC
Add SI705x temperature sensor with I2C interface and allow setting
temperature, VDD status and measurement resolution through properties.
This commit adds support for interfacing with it and implements
functionality for sensor's commands.

Datasheet: https://www.integrated-circuit.com/pdf/502/391/4.pdf
---
 hw/sensor/Kconfig      |   5 +
 hw/sensor/meson.build  |   1 +
 hw/sensor/tmp_si705x.c | 359 +++++++++++++++++++++++++++++++++++++++++
 hw/sensor/trace-events |   9 ++
 hw/sensor/trace.h      |   1 +
 meson.build            |   1 +
 6 files changed, 376 insertions(+)
 create mode 100644 hw/sensor/tmp_si705x.c
 create mode 100644 hw/sensor/trace-events
 create mode 100644 hw/sensor/trace.h
diff mbox series

Patch

diff --git a/hw/sensor/Kconfig b/hw/sensor/Kconfig
index bc6331b4ab..554ccbc5f6 100644
--- a/hw/sensor/Kconfig
+++ b/hw/sensor/Kconfig
@@ -43,3 +43,8 @@  config ISL_PMBUS_VR
 config MAX31785
     bool
     depends on PMBUS
+
+config SI705X
+    bool
+    depends on I2C
+    default y if I2C_DEVICES
diff --git a/hw/sensor/meson.build b/hw/sensor/meson.build
index 420fdc3359..fba9ccd255 100644
--- a/hw/sensor/meson.build
+++ b/hw/sensor/meson.build
@@ -8,3 +8,4 @@  system_ss.add(when: 'CONFIG_MAX34451', if_true: files('max34451.c'))
 system_ss.add(when: 'CONFIG_LSM303DLHC_MAG', if_true: files('lsm303dlhc_mag.c'))
 system_ss.add(when: 'CONFIG_ISL_PMBUS_VR', if_true: files('isl_pmbus_vr.c'))
 system_ss.add(when: 'CONFIG_MAX31785', if_true: files('max31785.c'))
+system_ss.add(when: 'CONFIG_SI705X', if_true: files('tmp_si705x.c'))
diff --git a/hw/sensor/tmp_si705x.c b/hw/sensor/tmp_si705x.c
new file mode 100644
index 0000000000..3b729974f0
--- /dev/null
+++ b/hw/sensor/tmp_si705x.c
@@ -0,0 +1,359 @@ 
+/*
+ * SI705X-A20-IM, Board Mount Temperature Sensors.
+ *
+ * https://www.integrated-circuit.com/pdf/502/391/4.pdf
+ *
+ * Copyright (c) 2024 Ilya Chichkov <i.chichkov@yadro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2 or later, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "hw/i2c/i2c.h"
+#include "qapi/error.h"
+#include "hw/register.h"
+#include "qapi/visitor.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+#define TYPE_SI705X "si705x"
+#define SI705X(obj) OBJECT_CHECK(Si705xState, (obj), TYPE_SI705X)
+
+#define  SI705X_CMD_MEASURE_HOLD_MASTER    0xe3
+#define  SI705X_CMD_MEASURE                0xf3
+#define  SI705X_CMD_RESET                  0xfe
+#define  SI705X_CMD_WRITE_USER_REG         0xe6
+#define  SI705X_CMD_READ_USER_REG          0xe7
+#define  SI705X_CMD_READ_ID_BYTE1_1        0xFA
+#define  SI705X_CMD_READ_ID_BYTE1_2        0x0F
+#define  SI705X_CMD_READ_ID_BYTE2_1        0xFC
+#define  SI705X_CMD_READ_ID_BYTE2_2        0xC9
+#define  SI705X_CMD_READ_FIRMWARE_REV_1    0x84
+#define  SI705X_CMD_READ_FIRMWARE_REV_2    0xB8
+
+#define SI705X_WRITE_MASK   0x81
+
+#define CRC_POLY            0x31
+#define BYTE7               0x80
+#define CRC_SEED            0
+
+#define  SI705X_UR1         0x00
+REG32(SI705X_UR1, 0x00)
+    FIELD(SI705X_UR1, RES0,  0,  1)
+    FIELD(SI705X_UR1, RSVD,  1,  5)
+    FIELD(SI705X_UR1, VDD,   6,  1)
+    FIELD(SI705X_UR1, RES1,  7,  1)
+
+typedef struct Si705xState {
+    /*< private >*/
+    I2CSlave i2c;
+
+    /*< public >*/
+    uint8_t user_reg;
+    uint8_t read_index;
+    uint8_t write_index;
+    uint8_t prev_command;
+    uint8_t command;
+    uint8_t io_buf[5];
+
+    uint8_t temperature[2];
+    uint8_t measurement_resolution;
+    uint8_t vdd_status;
+
+    uint64_t serial_num;
+    uint8_t fw_rev;
+} Si705xState;
+
+static uint8_t temp_sensor_crc(uint8_t *p_data, uint32_t len)
+{
+    uint8_t crc = CRC_SEED;
+
+    for (uint32_t i = 0U; i < len; i++) {
+        crc ^= p_data[i];
+
+        for (uint8_t j = 0U; j < 8; j++) {
+            if ((crc & BYTE7) != 0U) {
+                crc = (crc << 1U) ^ CRC_POLY;
+            } else {
+                crc <<= 1U;
+            }
+        }
+    }
+
+    return crc;
+}
+
+static void si705x_get_temperature(Object *obj, Visitor *v, const char *name,
+                                   void *opaque, Error **errp)
+{
+    Si705xState *s = SI705X(obj);
+
+    int16_t hv = s->temperature[0] << 8;
+    int64_t value = hv | s->temperature[1];
+
+    visit_type_int(v, name, &value, errp);
+}
+
+static void si705x_set_temperature(Object *obj, Visitor *v, const char *name,
+                                   void *opaque, Error **errp)
+{
+    Si705xState *s = SI705X(obj);
+    int64_t temp;
+
+    if (!visit_type_int(v, name, &temp, errp)) {
+        return;
+    }
+
+    /* Apply measurement resolution to set value */
+    temp &= ~((1 << (1 + s->measurement_resolution)) - 1);
+
+    s->temperature[0] = 0xff & (temp >> 8);
+    s->temperature[1] = 0xff & temp;
+}
+
+static void si705x_get_measure_res(Object *obj, Visitor *v, const char *name,
+                                   void *opaque, Error **errp)
+{
+    Si705xState *s = SI705X(obj);
+    int64_t value = s->measurement_resolution;
+    visit_type_int(v, name, &value, errp);
+}
+
+static void si705x_set_measure_res(Object *obj, Visitor *v, const char *name,
+                                   void *opaque, Error **errp)
+{
+    Si705xState *s = SI705X(obj);
+    int64_t temp;
+
+    if (!visit_type_int(v, name, &temp, errp)) {
+        return;
+    }
+
+    s->measurement_resolution = temp & 0x3;
+    s->user_reg |= ((s->measurement_resolution >> 1) << 7);
+    s->user_reg |= (s->measurement_resolution & 0x1);
+}
+
+static void si705x_get_vdds(Object *obj, Visitor *v, const char *name,
+                                   void *opaque, Error **errp)
+{
+    Si705xState *s = SI705X(obj);
+    int64_t value = s->vdd_status;
+    visit_type_int(v, name, &value, errp);
+}
+
+static void si705x_set_vdds(Object *obj, Visitor *v, const char *name,
+                                   void *opaque, Error **errp)
+{
+    Si705xState *s = SI705X(obj);
+    int64_t temp;
+
+    if (!visit_type_int(v, name, &temp, errp)) {
+        return;
+    }
+
+    s->vdd_status = temp & 0x1;
+    s->user_reg |= s->vdd_status << 6;
+}
+
+static void si705x_reset(I2CSlave *i2c)
+{
+    trace_si705x_reset();
+    Si705xState *s = SI705X(i2c);
+
+    s->user_reg = 0x3A;
+    s->read_index = 0;
+    s->write_index = 0;
+    s->command = 0;
+    s->measurement_resolution = 0;
+    s->vdd_status = 0;
+    s->serial_num = 0x37000000;
+    s->fw_rev = 0x20;
+
+    memset(s->io_buf, 0, sizeof(s->io_buf));
+    memset(s->temperature, 0, sizeof(s->temperature));
+}
+
+static void si705x_read(Si705xState *s)
+{
+    switch (s->command) {
+    case SI705X_CMD_MEASURE_HOLD_MASTER:
+    case SI705X_CMD_MEASURE:
+        s->io_buf[0] = s->temperature[0];
+        s->io_buf[1] = s->temperature[1];
+        s->io_buf[2] = temp_sensor_crc(s->io_buf, 2);
+        break;
+    case SI705X_CMD_READ_USER_REG:
+        s->io_buf[0] = s->user_reg;
+        break;
+    case SI705X_CMD_READ_ID_BYTE1_2:
+        if (s->prev_command == SI705X_CMD_READ_ID_BYTE1_1) {
+            s->io_buf[0] = (s->serial_num >> 56) & 0xff;
+            s->io_buf[1] = (s->serial_num >> 48) & 0xff;
+            s->io_buf[2] = (s->serial_num >> 40) & 0xff;
+            s->io_buf[3] = (s->serial_num >> 32) & 0xff;
+        }
+        break;
+    case SI705X_CMD_READ_ID_BYTE2_2:
+        if (s->prev_command == SI705X_CMD_READ_ID_BYTE1_1) {
+            s->io_buf[0] = (s->serial_num >> 24) & 0xff;
+            s->io_buf[1] = (s->serial_num >> 16) & 0xff;
+            s->io_buf[2] = (s->serial_num >> 8) & 0xff;
+            s->io_buf[3] = (s->serial_num >> 0) & 0xff;
+        }
+        break;
+    case SI705X_CMD_READ_FIRMWARE_REV_2:
+        if (s->prev_command == SI705X_CMD_READ_FIRMWARE_REV_1) {
+            s->io_buf[0] = s->fw_rev;
+        }
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                    "%s: write to unimplemented register\n", __func__);
+        break;
+    }
+    trace_si705x_read(s->command);
+}
+
+static void si705x_write(Si705xState *s)
+{
+    switch (s->command) {
+    case SI705X_CMD_RESET:
+        si705x_reset(&s->i2c);
+        break;
+    case SI705X_CMD_WRITE_USER_REG:
+        s->user_reg = s->io_buf[0] & SI705X_WRITE_MASK;
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                    "%s:  write to unimplemented register\n", __func__);
+        break;
+    }
+}
+
+static uint8_t si705x_rx(I2CSlave *i2c)
+{
+    Si705xState *s = SI705X(i2c);
+
+    if (s->read_index < sizeof(s->io_buf)) {
+        trace_si705x_rx(s->io_buf[s->read_index]);
+        return s->io_buf[s->read_index++];
+    }
+    trace_si705x_rx(0xff);
+    return 0xff;
+}
+
+static int si705x_tx(I2CSlave *i2c, uint8_t data)
+{
+    Si705xState *s = SI705X(i2c);
+    trace_si705x_write(s->write_index, sizeof(s->io_buf), data);
+
+    if (s->write_index == 0) {
+        s->command = data;
+        switch (s->command) {
+        case SI705X_CMD_READ_ID_BYTE1_1:
+        case SI705X_CMD_READ_ID_BYTE2_1:
+        case SI705X_CMD_READ_FIRMWARE_REV_1:
+            s->prev_command = s->command;
+            s->write_index = 0;
+            break;
+        default:
+            s->write_index++;
+            break;
+        }
+    } else {
+        if (s->write_index <= sizeof(s->io_buf)) {
+            s->io_buf[s->write_index - 1] = data;
+        }
+        s->write_index++;
+        si705x_write(s);
+    }
+
+    return 0;
+}
+
+static int si705x_event(I2CSlave *i2c, enum i2c_event event)
+{
+    Si705xState *s = SI705X(i2c);
+    trace_si705x_event(event);
+
+    switch (event) {
+    case I2C_START_RECV:
+        si705x_read(s);
+        break;
+    case I2C_FINISH:
+        s->read_index = 0;
+        s->write_index = 0;
+    default:
+        break;
+    }
+    return 0;
+}
+
+static const VMStateDescription vmstate_si705x = {
+    .name = "SI705X",
+    .version_id = 0,
+    .minimum_version_id = 0,
+};
+
+static void si705x_realize(DeviceState *dev, Error **errp)
+{
+    I2CSlave *i2c = I2C_SLAVE(dev);
+    Si705xState *s = SI705X(i2c);
+
+    si705x_reset(&s->i2c);
+}
+
+static void si705x_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+
+    k->event = si705x_event;
+    k->recv = si705x_rx;
+    k->send = si705x_tx;
+    dc->realize = si705x_realize;
+    dc->vmsd = &vmstate_si705x;
+
+    trace_si705x_init();
+}
+
+static void si705x_initfn(Object *obj)
+{
+    object_property_add(obj, "temperature", "int",
+                        si705x_get_temperature,
+                        si705x_set_temperature, NULL, NULL);
+    object_property_add(obj, "vdds", "int",
+                        si705x_get_vdds,
+                        si705x_set_vdds, NULL, NULL);
+    object_property_add(obj, "resolution", "int",
+                        si705x_get_measure_res,
+                        si705x_set_measure_res, NULL, NULL);
+}
+
+static const TypeInfo si705x_info = {
+    .name          = TYPE_SI705X,
+    .parent        = TYPE_I2C_SLAVE,
+    .instance_size = sizeof(Si705xState),
+    .instance_init = si705x_initfn,
+    .class_init    = si705x_class_init,
+};
+
+static void si705x_register_types(void)
+{
+    type_register_static(&si705x_info);
+}
+
+type_init(si705x_register_types)
diff --git a/hw/sensor/trace-events b/hw/sensor/trace-events
new file mode 100644
index 0000000000..42e19da98e
--- /dev/null
+++ b/hw/sensor/trace-events
@@ -0,0 +1,9 @@ 
+# See docs/devel/tracing.rst for syntax documentation.
+
+# tmp_si705x.c
+si705x_write(uint8_t index, uint8_t buf_len, uint8_t data) "index: [%d/%d], data: 0x%x"
+si705x_read(uint8_t cmd) "Command: 0x%x"
+si705x_rx(uint8_t data) "Read RX: 0x%x"
+si705x_event(uint8_t event) "Event: 0x%x"
+si705x_init(void)
+si705x_reset(void)
diff --git a/hw/sensor/trace.h b/hw/sensor/trace.h
new file mode 100644
index 0000000000..e4721560b0
--- /dev/null
+++ b/hw/sensor/trace.h
@@ -0,0 +1 @@ 
+#include "trace/trace-hw_sensor.h"
diff --git a/meson.build b/meson.build
index d0329966f1..385c903253 100644
--- a/meson.build
+++ b/meson.build
@@ -3330,6 +3330,7 @@  if have_system
     'hw/watchdog',
     'hw/xen',
     'hw/gpio',
+    'hw/sensor',
     'migration',
     'net',
     'system',