| /* |
| * SMSC EMC141X temperature sensor. |
| * |
| * Copyright (c) 2020 Bytedance Corporation |
| * Written by John Wang <wangzhiqiang.bj@bytedance.com> |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation; either version 2 or |
| * (at your option) version 3 of the License. |
| * |
| * This program is distributed in the hope that 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 "hw/i2c/i2c.h" |
| #include "migration/vmstate.h" |
| #include "qapi/error.h" |
| #include "qapi/visitor.h" |
| #include "qemu/module.h" |
| #include "qom/object.h" |
| #include "hw/sensor/emc141x_regs.h" |
| |
| #define SENSORS_COUNT_MAX 4 |
| |
| struct EMC141XState { |
| I2CSlave parent_obj; |
| struct { |
| uint8_t raw_temp_min; |
| uint8_t raw_temp_current; |
| uint8_t raw_temp_max; |
| } sensor[SENSORS_COUNT_MAX]; |
| uint8_t len; |
| uint8_t data; |
| uint8_t pointer; |
| }; |
| |
| struct EMC141XClass { |
| I2CSlaveClass parent_class; |
| uint8_t model; |
| unsigned sensors_count; |
| }; |
| |
| #define TYPE_EMC141X "emc141x" |
| OBJECT_DECLARE_TYPE(EMC141XState, EMC141XClass, EMC141X) |
| |
| static void emc141x_get_temperature(Object *obj, Visitor *v, const char *name, |
| void *opaque, Error **errp) |
| { |
| EMC141XState *s = EMC141X(obj); |
| EMC141XClass *sc = EMC141X_GET_CLASS(s); |
| int64_t value; |
| unsigned tempid; |
| |
| if (sscanf(name, "temperature%u", &tempid) != 1) { |
| error_setg(errp, "error reading %s: %s", name, g_strerror(errno)); |
| return; |
| } |
| |
| if (tempid >= sc->sensors_count) { |
| error_setg(errp, "error reading %s", name); |
| return; |
| } |
| |
| value = s->sensor[tempid].raw_temp_current * 1000; |
| |
| visit_type_int(v, name, &value, errp); |
| } |
| |
| static void emc141x_set_temperature(Object *obj, Visitor *v, const char *name, |
| void *opaque, Error **errp) |
| { |
| EMC141XState *s = EMC141X(obj); |
| EMC141XClass *sc = EMC141X_GET_CLASS(s); |
| int64_t temp; |
| unsigned tempid; |
| |
| if (!visit_type_int(v, name, &temp, errp)) { |
| return; |
| } |
| |
| if (sscanf(name, "temperature%u", &tempid) != 1) { |
| error_setg(errp, "error reading %s: %s", name, g_strerror(errno)); |
| return; |
| } |
| |
| if (tempid >= sc->sensors_count) { |
| error_setg(errp, "error reading %s", name); |
| return; |
| } |
| |
| s->sensor[tempid].raw_temp_current = temp / 1000; |
| } |
| |
| static void emc141x_read(EMC141XState *s) |
| { |
| EMC141XClass *sc = EMC141X_GET_CLASS(s); |
| switch (s->pointer) { |
| case EMC141X_DEVICE_ID: |
| s->data = sc->model; |
| break; |
| case EMC141X_MANUFACTURER_ID: |
| s->data = MANUFACTURER_ID; |
| break; |
| case EMC141X_REVISION: |
| s->data = REVISION; |
| break; |
| case EMC141X_TEMP_HIGH0: |
| s->data = s->sensor[0].raw_temp_current; |
| break; |
| case EMC141X_TEMP_HIGH1: |
| s->data = s->sensor[1].raw_temp_current; |
| break; |
| case EMC141X_TEMP_HIGH2: |
| s->data = s->sensor[2].raw_temp_current; |
| break; |
| case EMC141X_TEMP_HIGH3: |
| s->data = s->sensor[3].raw_temp_current; |
| break; |
| case EMC141X_TEMP_MAX_HIGH0: |
| s->data = s->sensor[0].raw_temp_max; |
| break; |
| case EMC141X_TEMP_MAX_HIGH1: |
| s->data = s->sensor[1].raw_temp_max; |
| break; |
| case EMC141X_TEMP_MAX_HIGH2: |
| s->data = s->sensor[2].raw_temp_max; |
| break; |
| case EMC141X_TEMP_MAX_HIGH3: |
| s->data = s->sensor[3].raw_temp_max; |
| break; |
| case EMC141X_TEMP_MIN_HIGH0: |
| s->data = s->sensor[0].raw_temp_min; |
| break; |
| case EMC141X_TEMP_MIN_HIGH1: |
| s->data = s->sensor[1].raw_temp_min; |
| break; |
| case EMC141X_TEMP_MIN_HIGH2: |
| s->data = s->sensor[2].raw_temp_min; |
| break; |
| case EMC141X_TEMP_MIN_HIGH3: |
| s->data = s->sensor[3].raw_temp_min; |
| break; |
| default: |
| s->data = 0; |
| } |
| } |
| |
| static void emc141x_write(EMC141XState *s) |
| { |
| switch (s->pointer) { |
| case EMC141X_TEMP_MAX_HIGH0: |
| s->sensor[0].raw_temp_max = s->data; |
| break; |
| case EMC141X_TEMP_MAX_HIGH1: |
| s->sensor[1].raw_temp_max = s->data; |
| break; |
| case EMC141X_TEMP_MAX_HIGH2: |
| s->sensor[2].raw_temp_max = s->data; |
| break; |
| case EMC141X_TEMP_MAX_HIGH3: |
| s->sensor[3].raw_temp_max = s->data; |
| break; |
| case EMC141X_TEMP_MIN_HIGH0: |
| s->sensor[0].raw_temp_min = s->data; |
| break; |
| case EMC141X_TEMP_MIN_HIGH1: |
| s->sensor[1].raw_temp_min = s->data; |
| break; |
| case EMC141X_TEMP_MIN_HIGH2: |
| s->sensor[2].raw_temp_min = s->data; |
| break; |
| case EMC141X_TEMP_MIN_HIGH3: |
| s->sensor[3].raw_temp_min = s->data; |
| break; |
| default: |
| s->data = 0; |
| } |
| } |
| |
| static uint8_t emc141x_rx(I2CSlave *i2c) |
| { |
| EMC141XState *s = EMC141X(i2c); |
| |
| if (s->len == 0) { |
| s->len++; |
| return s->data; |
| } else { |
| return 0xff; |
| } |
| } |
| |
| static int emc141x_tx(I2CSlave *i2c, uint8_t data) |
| { |
| EMC141XState *s = EMC141X(i2c); |
| |
| if (s->len == 0) { |
| /* first byte is the reg pointer */ |
| s->pointer = data; |
| s->len++; |
| } else if (s->len == 1) { |
| s->data = data; |
| emc141x_write(s); |
| } |
| |
| return 0; |
| } |
| |
| static int emc141x_event(I2CSlave *i2c, enum i2c_event event) |
| { |
| EMC141XState *s = EMC141X(i2c); |
| |
| if (event == I2C_START_RECV) { |
| emc141x_read(s); |
| } |
| |
| s->len = 0; |
| return 0; |
| } |
| |
| static const VMStateDescription vmstate_emc141x = { |
| .name = "EMC141X", |
| .version_id = 0, |
| .minimum_version_id = 0, |
| .fields = (const VMStateField[]) { |
| VMSTATE_UINT8(len, EMC141XState), |
| VMSTATE_UINT8(data, EMC141XState), |
| VMSTATE_UINT8(pointer, EMC141XState), |
| VMSTATE_I2C_SLAVE(parent_obj, EMC141XState), |
| VMSTATE_END_OF_LIST() |
| } |
| }; |
| |
| static void emc141x_reset(DeviceState *dev) |
| { |
| EMC141XState *s = EMC141X(dev); |
| int i; |
| |
| for (i = 0; i < SENSORS_COUNT_MAX; i++) { |
| s->sensor[i].raw_temp_max = 0x55; |
| } |
| s->pointer = 0; |
| s->len = 0; |
| } |
| |
| static void emc141x_initfn(Object *obj) |
| { |
| object_property_add(obj, "temperature0", "int", |
| emc141x_get_temperature, |
| emc141x_set_temperature, NULL, NULL); |
| object_property_add(obj, "temperature1", "int", |
| emc141x_get_temperature, |
| emc141x_set_temperature, NULL, NULL); |
| object_property_add(obj, "temperature2", "int", |
| emc141x_get_temperature, |
| emc141x_set_temperature, NULL, NULL); |
| object_property_add(obj, "temperature3", "int", |
| emc141x_get_temperature, |
| emc141x_set_temperature, NULL, NULL); |
| } |
| |
| static void emc141x_class_init(ObjectClass *klass, void *data) |
| { |
| DeviceClass *dc = DEVICE_CLASS(klass); |
| I2CSlaveClass *k = I2C_SLAVE_CLASS(klass); |
| |
| device_class_set_legacy_reset(dc, emc141x_reset); |
| k->event = emc141x_event; |
| k->recv = emc141x_rx; |
| k->send = emc141x_tx; |
| dc->vmsd = &vmstate_emc141x; |
| } |
| |
| static void emc1413_class_init(ObjectClass *klass, void *data) |
| { |
| EMC141XClass *ec = EMC141X_CLASS(klass); |
| |
| emc141x_class_init(klass, data); |
| ec->model = EMC1413_DEVICE_ID; |
| ec->sensors_count = 3; |
| } |
| |
| static void emc1414_class_init(ObjectClass *klass, void *data) |
| { |
| EMC141XClass *ec = EMC141X_CLASS(klass); |
| |
| emc141x_class_init(klass, data); |
| ec->model = EMC1414_DEVICE_ID; |
| ec->sensors_count = 4; |
| } |
| |
| static const TypeInfo emc141x_info = { |
| .name = TYPE_EMC141X, |
| .parent = TYPE_I2C_SLAVE, |
| .instance_size = sizeof(EMC141XState), |
| .class_size = sizeof(EMC141XClass), |
| .instance_init = emc141x_initfn, |
| .abstract = true, |
| }; |
| |
| static const TypeInfo emc1413_info = { |
| .name = "emc1413", |
| .parent = TYPE_EMC141X, |
| .class_init = emc1413_class_init, |
| }; |
| |
| static const TypeInfo emc1414_info = { |
| .name = "emc1414", |
| .parent = TYPE_EMC141X, |
| .class_init = emc1414_class_init, |
| }; |
| |
| static void emc141x_register_types(void) |
| { |
| type_register_static(&emc141x_info); |
| type_register_static(&emc1413_info); |
| type_register_static(&emc1414_info); |
| } |
| |
| type_init(emc141x_register_types) |