14fd1ebb1SKevin Townsend /* 24fd1ebb1SKevin Townsend * LSM303DLHC I2C magnetometer. 34fd1ebb1SKevin Townsend * 44fd1ebb1SKevin Townsend * Copyright (C) 2021 Linaro Ltd. 54fd1ebb1SKevin Townsend * Written by Kevin Townsend <kevin.townsend@linaro.org> 64fd1ebb1SKevin Townsend * 74fd1ebb1SKevin Townsend * Based on: https://www.st.com/resource/en/datasheet/lsm303dlhc.pdf 84fd1ebb1SKevin Townsend * 94fd1ebb1SKevin Townsend * SPDX-License-Identifier: GPL-2.0-or-later 104fd1ebb1SKevin Townsend */ 114fd1ebb1SKevin Townsend 124fd1ebb1SKevin Townsend /* 134fd1ebb1SKevin Townsend * The I2C address associated with this device is set on the command-line when 144fd1ebb1SKevin Townsend * initialising the machine, but the following address is standard: 0x1E. 154fd1ebb1SKevin Townsend * 164fd1ebb1SKevin Townsend * Get and set functions for 'mag-x', 'mag-y' and 'mag-z' assume that 174fd1ebb1SKevin Townsend * 1 = 0.001 uT. (NOTE the 1 gauss = 100 uT, so setting a value of 100,000 184fd1ebb1SKevin Townsend * would be equal to 1 gauss or 100 uT.) 194fd1ebb1SKevin Townsend * 204fd1ebb1SKevin Townsend * Get and set functions for 'temperature' assume that 1 = 0.001 C, so 23.6 C 214fd1ebb1SKevin Townsend * would be equal to 23600. 224fd1ebb1SKevin Townsend */ 234fd1ebb1SKevin Townsend 244fd1ebb1SKevin Townsend #include "qemu/osdep.h" 254fd1ebb1SKevin Townsend #include "hw/i2c/i2c.h" 264fd1ebb1SKevin Townsend #include "migration/vmstate.h" 274fd1ebb1SKevin Townsend #include "qapi/error.h" 284fd1ebb1SKevin Townsend #include "qapi/visitor.h" 294fd1ebb1SKevin Townsend #include "qemu/module.h" 304fd1ebb1SKevin Townsend #include "qemu/log.h" 314fd1ebb1SKevin Townsend #include "qemu/bswap.h" 324fd1ebb1SKevin Townsend 334fd1ebb1SKevin Townsend enum LSM303DLHCMagReg { 344fd1ebb1SKevin Townsend LSM303DLHC_MAG_REG_CRA = 0x00, 354fd1ebb1SKevin Townsend LSM303DLHC_MAG_REG_CRB = 0x01, 364fd1ebb1SKevin Townsend LSM303DLHC_MAG_REG_MR = 0x02, 374fd1ebb1SKevin Townsend LSM303DLHC_MAG_REG_OUT_X_H = 0x03, 384fd1ebb1SKevin Townsend LSM303DLHC_MAG_REG_OUT_X_L = 0x04, 394fd1ebb1SKevin Townsend LSM303DLHC_MAG_REG_OUT_Z_H = 0x05, 404fd1ebb1SKevin Townsend LSM303DLHC_MAG_REG_OUT_Z_L = 0x06, 414fd1ebb1SKevin Townsend LSM303DLHC_MAG_REG_OUT_Y_H = 0x07, 424fd1ebb1SKevin Townsend LSM303DLHC_MAG_REG_OUT_Y_L = 0x08, 434fd1ebb1SKevin Townsend LSM303DLHC_MAG_REG_SR = 0x09, 444fd1ebb1SKevin Townsend LSM303DLHC_MAG_REG_IRA = 0x0A, 454fd1ebb1SKevin Townsend LSM303DLHC_MAG_REG_IRB = 0x0B, 464fd1ebb1SKevin Townsend LSM303DLHC_MAG_REG_IRC = 0x0C, 474fd1ebb1SKevin Townsend LSM303DLHC_MAG_REG_TEMP_OUT_H = 0x31, 484fd1ebb1SKevin Townsend LSM303DLHC_MAG_REG_TEMP_OUT_L = 0x32 494fd1ebb1SKevin Townsend }; 504fd1ebb1SKevin Townsend 514fd1ebb1SKevin Townsend typedef struct LSM303DLHCMagState { 524fd1ebb1SKevin Townsend I2CSlave parent_obj; 534fd1ebb1SKevin Townsend uint8_t cra; 544fd1ebb1SKevin Townsend uint8_t crb; 554fd1ebb1SKevin Townsend uint8_t mr; 564fd1ebb1SKevin Townsend int16_t x; 574fd1ebb1SKevin Townsend int16_t z; 584fd1ebb1SKevin Townsend int16_t y; 594fd1ebb1SKevin Townsend int16_t x_lock; 604fd1ebb1SKevin Townsend int16_t z_lock; 614fd1ebb1SKevin Townsend int16_t y_lock; 624fd1ebb1SKevin Townsend uint8_t sr; 634fd1ebb1SKevin Townsend uint8_t ira; 644fd1ebb1SKevin Townsend uint8_t irb; 654fd1ebb1SKevin Townsend uint8_t irc; 664fd1ebb1SKevin Townsend int16_t temperature; 674fd1ebb1SKevin Townsend int16_t temperature_lock; 684fd1ebb1SKevin Townsend uint8_t len; 694fd1ebb1SKevin Townsend uint8_t buf; 704fd1ebb1SKevin Townsend uint8_t pointer; 714fd1ebb1SKevin Townsend } LSM303DLHCMagState; 724fd1ebb1SKevin Townsend 734fd1ebb1SKevin Townsend #define TYPE_LSM303DLHC_MAG "lsm303dlhc_mag" 744fd1ebb1SKevin Townsend OBJECT_DECLARE_SIMPLE_TYPE(LSM303DLHCMagState, LSM303DLHC_MAG) 754fd1ebb1SKevin Townsend 764fd1ebb1SKevin Townsend /* 774fd1ebb1SKevin Townsend * Conversion factor from Gauss to sensor values for each GN gain setting, 784fd1ebb1SKevin Townsend * in units "lsb per Gauss" (see data sheet table 3). There is no documented 794fd1ebb1SKevin Townsend * behaviour if the GN setting in CRB is incorrectly set to 0b000; 804fd1ebb1SKevin Townsend * we arbitrarily make it the same as 0b001. 814fd1ebb1SKevin Townsend */ 824fd1ebb1SKevin Townsend uint32_t xy_gain[] = { 1100, 1100, 855, 670, 450, 400, 330, 230 }; 834fd1ebb1SKevin Townsend uint32_t z_gain[] = { 980, 980, 760, 600, 400, 355, 295, 205 }; 844fd1ebb1SKevin Townsend 854fd1ebb1SKevin Townsend static void lsm303dlhc_mag_get_x(Object *obj, Visitor *v, const char *name, 864fd1ebb1SKevin Townsend void *opaque, Error **errp) 874fd1ebb1SKevin Townsend { 884fd1ebb1SKevin Townsend LSM303DLHCMagState *s = LSM303DLHC_MAG(obj); 894fd1ebb1SKevin Townsend int gm = extract32(s->crb, 5, 3); 904fd1ebb1SKevin Townsend 914fd1ebb1SKevin Townsend /* Convert to uT where 1000 = 1 uT. Conversion factor depends on gain. */ 924fd1ebb1SKevin Townsend int64_t value = muldiv64(s->x, 100000, xy_gain[gm]); 934fd1ebb1SKevin Townsend visit_type_int(v, name, &value, errp); 944fd1ebb1SKevin Townsend } 954fd1ebb1SKevin Townsend 964fd1ebb1SKevin Townsend static void lsm303dlhc_mag_get_y(Object *obj, Visitor *v, const char *name, 974fd1ebb1SKevin Townsend void *opaque, Error **errp) 984fd1ebb1SKevin Townsend { 994fd1ebb1SKevin Townsend LSM303DLHCMagState *s = LSM303DLHC_MAG(obj); 1004fd1ebb1SKevin Townsend int gm = extract32(s->crb, 5, 3); 1014fd1ebb1SKevin Townsend 1024fd1ebb1SKevin Townsend /* Convert to uT where 1000 = 1 uT. Conversion factor depends on gain. */ 1034fd1ebb1SKevin Townsend int64_t value = muldiv64(s->y, 100000, xy_gain[gm]); 1044fd1ebb1SKevin Townsend visit_type_int(v, name, &value, errp); 1054fd1ebb1SKevin Townsend } 1064fd1ebb1SKevin Townsend 1074fd1ebb1SKevin Townsend static void lsm303dlhc_mag_get_z(Object *obj, Visitor *v, const char *name, 1084fd1ebb1SKevin Townsend void *opaque, Error **errp) 1094fd1ebb1SKevin Townsend { 1104fd1ebb1SKevin Townsend LSM303DLHCMagState *s = LSM303DLHC_MAG(obj); 1114fd1ebb1SKevin Townsend int gm = extract32(s->crb, 5, 3); 1124fd1ebb1SKevin Townsend 1134fd1ebb1SKevin Townsend /* Convert to uT where 1000 = 1 uT. Conversion factor depends on gain. */ 1144fd1ebb1SKevin Townsend int64_t value = muldiv64(s->z, 100000, z_gain[gm]); 1154fd1ebb1SKevin Townsend visit_type_int(v, name, &value, errp); 1164fd1ebb1SKevin Townsend } 1174fd1ebb1SKevin Townsend 1184fd1ebb1SKevin Townsend static void lsm303dlhc_mag_set_x(Object *obj, Visitor *v, const char *name, 1194fd1ebb1SKevin Townsend void *opaque, Error **errp) 1204fd1ebb1SKevin Townsend { 1214fd1ebb1SKevin Townsend LSM303DLHCMagState *s = LSM303DLHC_MAG(obj); 1224fd1ebb1SKevin Townsend int64_t value; 1234fd1ebb1SKevin Townsend int64_t reg; 1244fd1ebb1SKevin Townsend int gm = extract32(s->crb, 5, 3); 1254fd1ebb1SKevin Townsend 1264fd1ebb1SKevin Townsend if (!visit_type_int(v, name, &value, errp)) { 1274fd1ebb1SKevin Townsend return; 1284fd1ebb1SKevin Townsend } 1294fd1ebb1SKevin Townsend 1304fd1ebb1SKevin Townsend reg = muldiv64(value, xy_gain[gm], 100000); 1314fd1ebb1SKevin Townsend 1324fd1ebb1SKevin Townsend /* Make sure we are within a 12-bit limit. */ 1334fd1ebb1SKevin Townsend if (reg > 2047 || reg < -2048) { 1344fd1ebb1SKevin Townsend error_setg(errp, "value %" PRId64 " out of register's range", value); 1354fd1ebb1SKevin Townsend return; 1364fd1ebb1SKevin Townsend } 1374fd1ebb1SKevin Townsend 1384fd1ebb1SKevin Townsend s->x = (int16_t)reg; 1394fd1ebb1SKevin Townsend } 1404fd1ebb1SKevin Townsend 1414fd1ebb1SKevin Townsend static void lsm303dlhc_mag_set_y(Object *obj, Visitor *v, const char *name, 1424fd1ebb1SKevin Townsend void *opaque, Error **errp) 1434fd1ebb1SKevin Townsend { 1444fd1ebb1SKevin Townsend LSM303DLHCMagState *s = LSM303DLHC_MAG(obj); 1454fd1ebb1SKevin Townsend int64_t value; 1464fd1ebb1SKevin Townsend int64_t reg; 1474fd1ebb1SKevin Townsend int gm = extract32(s->crb, 5, 3); 1484fd1ebb1SKevin Townsend 1494fd1ebb1SKevin Townsend if (!visit_type_int(v, name, &value, errp)) { 1504fd1ebb1SKevin Townsend return; 1514fd1ebb1SKevin Townsend } 1524fd1ebb1SKevin Townsend 1534fd1ebb1SKevin Townsend reg = muldiv64(value, xy_gain[gm], 100000); 1544fd1ebb1SKevin Townsend 1554fd1ebb1SKevin Townsend /* Make sure we are within a 12-bit limit. */ 1564fd1ebb1SKevin Townsend if (reg > 2047 || reg < -2048) { 1574fd1ebb1SKevin Townsend error_setg(errp, "value %" PRId64 " out of register's range", value); 1584fd1ebb1SKevin Townsend return; 1594fd1ebb1SKevin Townsend } 1604fd1ebb1SKevin Townsend 1614fd1ebb1SKevin Townsend s->y = (int16_t)reg; 1624fd1ebb1SKevin Townsend } 1634fd1ebb1SKevin Townsend 1644fd1ebb1SKevin Townsend static void lsm303dlhc_mag_set_z(Object *obj, Visitor *v, const char *name, 1654fd1ebb1SKevin Townsend void *opaque, Error **errp) 1664fd1ebb1SKevin Townsend { 1674fd1ebb1SKevin Townsend LSM303DLHCMagState *s = LSM303DLHC_MAG(obj); 1684fd1ebb1SKevin Townsend int64_t value; 1694fd1ebb1SKevin Townsend int64_t reg; 1704fd1ebb1SKevin Townsend int gm = extract32(s->crb, 5, 3); 1714fd1ebb1SKevin Townsend 1724fd1ebb1SKevin Townsend if (!visit_type_int(v, name, &value, errp)) { 1734fd1ebb1SKevin Townsend return; 1744fd1ebb1SKevin Townsend } 1754fd1ebb1SKevin Townsend 1764fd1ebb1SKevin Townsend reg = muldiv64(value, z_gain[gm], 100000); 1774fd1ebb1SKevin Townsend 1784fd1ebb1SKevin Townsend /* Make sure we are within a 12-bit limit. */ 1794fd1ebb1SKevin Townsend if (reg > 2047 || reg < -2048) { 1804fd1ebb1SKevin Townsend error_setg(errp, "value %" PRId64 " out of register's range", value); 1814fd1ebb1SKevin Townsend return; 1824fd1ebb1SKevin Townsend } 1834fd1ebb1SKevin Townsend 1844fd1ebb1SKevin Townsend s->z = (int16_t)reg; 1854fd1ebb1SKevin Townsend } 1864fd1ebb1SKevin Townsend 1874fd1ebb1SKevin Townsend /* 1884fd1ebb1SKevin Townsend * Get handler for the temperature property. 1894fd1ebb1SKevin Townsend */ 1904fd1ebb1SKevin Townsend static void lsm303dlhc_mag_get_temperature(Object *obj, Visitor *v, 1914fd1ebb1SKevin Townsend const char *name, void *opaque, 1924fd1ebb1SKevin Townsend Error **errp) 1934fd1ebb1SKevin Townsend { 1944fd1ebb1SKevin Townsend LSM303DLHCMagState *s = LSM303DLHC_MAG(obj); 1954fd1ebb1SKevin Townsend int64_t value; 1964fd1ebb1SKevin Townsend 1974fd1ebb1SKevin Townsend /* Convert to 1 lsb = 0.125 C to 1 = 0.001 C for 'temperature' property. */ 1984fd1ebb1SKevin Townsend value = s->temperature * 125; 1994fd1ebb1SKevin Townsend 2004fd1ebb1SKevin Townsend visit_type_int(v, name, &value, errp); 2014fd1ebb1SKevin Townsend } 2024fd1ebb1SKevin Townsend 2034fd1ebb1SKevin Townsend /* 2044fd1ebb1SKevin Townsend * Set handler for the temperature property. 2054fd1ebb1SKevin Townsend */ 2064fd1ebb1SKevin Townsend static void lsm303dlhc_mag_set_temperature(Object *obj, Visitor *v, 2074fd1ebb1SKevin Townsend const char *name, void *opaque, 2084fd1ebb1SKevin Townsend Error **errp) 2094fd1ebb1SKevin Townsend { 2104fd1ebb1SKevin Townsend LSM303DLHCMagState *s = LSM303DLHC_MAG(obj); 2114fd1ebb1SKevin Townsend int64_t value; 2124fd1ebb1SKevin Townsend 2134fd1ebb1SKevin Townsend if (!visit_type_int(v, name, &value, errp)) { 2144fd1ebb1SKevin Townsend return; 2154fd1ebb1SKevin Townsend } 2164fd1ebb1SKevin Townsend 2174fd1ebb1SKevin Townsend /* Input temperature is in 0.001 C units. Convert to 1 lsb = 0.125 C. */ 2184fd1ebb1SKevin Townsend value /= 125; 2194fd1ebb1SKevin Townsend 2204fd1ebb1SKevin Townsend if (value > 2047 || value < -2048) { 2214fd1ebb1SKevin Townsend error_setg(errp, "value %" PRId64 " lsb is out of range", value); 2224fd1ebb1SKevin Townsend return; 2234fd1ebb1SKevin Townsend } 2244fd1ebb1SKevin Townsend 2254fd1ebb1SKevin Townsend s->temperature = (int16_t)value; 2264fd1ebb1SKevin Townsend } 2274fd1ebb1SKevin Townsend 2284fd1ebb1SKevin Townsend /* 2294fd1ebb1SKevin Townsend * Callback handler whenever a 'I2C_START_RECV' (read) event is received. 2304fd1ebb1SKevin Townsend */ 2314fd1ebb1SKevin Townsend static void lsm303dlhc_mag_read(LSM303DLHCMagState *s) 2324fd1ebb1SKevin Townsend { 2334fd1ebb1SKevin Townsend /* 2344fd1ebb1SKevin Townsend * Set the LOCK bit whenever a new read attempt is made. This will be 2354fd1ebb1SKevin Townsend * cleared in I2C_FINISH. Note that DRDY is always set to 1 in this driver. 2364fd1ebb1SKevin Townsend */ 2374fd1ebb1SKevin Townsend s->sr = 0x3; 2384fd1ebb1SKevin Townsend 2394fd1ebb1SKevin Townsend /* 2404fd1ebb1SKevin Townsend * Copy the current X/Y/Z and temp. values into the locked registers so 2414fd1ebb1SKevin Townsend * that 'mag-x', 'mag-y', 'mag-z' and 'temperature' can continue to be 2424fd1ebb1SKevin Townsend * updated via QOM, etc., without corrupting the current read event. 2434fd1ebb1SKevin Townsend */ 2444fd1ebb1SKevin Townsend s->x_lock = s->x; 2454fd1ebb1SKevin Townsend s->z_lock = s->z; 2464fd1ebb1SKevin Townsend s->y_lock = s->y; 2474fd1ebb1SKevin Townsend s->temperature_lock = s->temperature; 2484fd1ebb1SKevin Townsend } 2494fd1ebb1SKevin Townsend 2504fd1ebb1SKevin Townsend /* 2514fd1ebb1SKevin Townsend * Callback handler whenever a 'I2C_FINISH' event is received. 2524fd1ebb1SKevin Townsend */ 2534fd1ebb1SKevin Townsend static void lsm303dlhc_mag_finish(LSM303DLHCMagState *s) 2544fd1ebb1SKevin Townsend { 2554fd1ebb1SKevin Townsend /* 2564fd1ebb1SKevin Townsend * Clear the LOCK bit when the read attempt terminates. 2574fd1ebb1SKevin Townsend * This bit is initially set in the I2C_START_RECV handler. 2584fd1ebb1SKevin Townsend */ 2594fd1ebb1SKevin Townsend s->sr = 0x1; 2604fd1ebb1SKevin Townsend } 2614fd1ebb1SKevin Townsend 2624fd1ebb1SKevin Townsend /* 2634fd1ebb1SKevin Townsend * Callback handler when a device attempts to write to a register. 2644fd1ebb1SKevin Townsend */ 2654fd1ebb1SKevin Townsend static void lsm303dlhc_mag_write(LSM303DLHCMagState *s) 2664fd1ebb1SKevin Townsend { 2674fd1ebb1SKevin Townsend switch (s->pointer) { 2684fd1ebb1SKevin Townsend case LSM303DLHC_MAG_REG_CRA: 2694fd1ebb1SKevin Townsend s->cra = s->buf; 2704fd1ebb1SKevin Townsend break; 2714fd1ebb1SKevin Townsend case LSM303DLHC_MAG_REG_CRB: 2724fd1ebb1SKevin Townsend /* Make sure gain is at least 1, falling back to 1 on an error. */ 2734fd1ebb1SKevin Townsend if (s->buf >> 5 == 0) { 2744fd1ebb1SKevin Townsend s->buf = 1 << 5; 2754fd1ebb1SKevin Townsend } 2764fd1ebb1SKevin Townsend s->crb = s->buf; 2774fd1ebb1SKevin Townsend break; 2784fd1ebb1SKevin Townsend case LSM303DLHC_MAG_REG_MR: 2794fd1ebb1SKevin Townsend s->mr = s->buf; 2804fd1ebb1SKevin Townsend break; 2814fd1ebb1SKevin Townsend case LSM303DLHC_MAG_REG_SR: 2824fd1ebb1SKevin Townsend s->sr = s->buf; 2834fd1ebb1SKevin Townsend break; 2844fd1ebb1SKevin Townsend case LSM303DLHC_MAG_REG_IRA: 2854fd1ebb1SKevin Townsend s->ira = s->buf; 2864fd1ebb1SKevin Townsend break; 2874fd1ebb1SKevin Townsend case LSM303DLHC_MAG_REG_IRB: 2884fd1ebb1SKevin Townsend s->irb = s->buf; 2894fd1ebb1SKevin Townsend break; 2904fd1ebb1SKevin Townsend case LSM303DLHC_MAG_REG_IRC: 2914fd1ebb1SKevin Townsend s->irc = s->buf; 2924fd1ebb1SKevin Townsend break; 2934fd1ebb1SKevin Townsend default: 2944fd1ebb1SKevin Townsend qemu_log_mask(LOG_GUEST_ERROR, "reg is read-only: 0x%02X", s->buf); 2954fd1ebb1SKevin Townsend break; 2964fd1ebb1SKevin Townsend } 2974fd1ebb1SKevin Townsend } 2984fd1ebb1SKevin Townsend 2994fd1ebb1SKevin Townsend /* 3004fd1ebb1SKevin Townsend * Low-level master-to-slave transaction handler. 3014fd1ebb1SKevin Townsend */ 3024fd1ebb1SKevin Townsend static int lsm303dlhc_mag_send(I2CSlave *i2c, uint8_t data) 3034fd1ebb1SKevin Townsend { 3044fd1ebb1SKevin Townsend LSM303DLHCMagState *s = LSM303DLHC_MAG(i2c); 3054fd1ebb1SKevin Townsend 3064fd1ebb1SKevin Townsend if (s->len == 0) { 3074fd1ebb1SKevin Townsend /* First byte is the reg pointer */ 3084fd1ebb1SKevin Townsend s->pointer = data; 3094fd1ebb1SKevin Townsend s->len++; 3104fd1ebb1SKevin Townsend } else if (s->len == 1) { 3114fd1ebb1SKevin Townsend /* Second byte is the new register value. */ 3124fd1ebb1SKevin Townsend s->buf = data; 3134fd1ebb1SKevin Townsend lsm303dlhc_mag_write(s); 3144fd1ebb1SKevin Townsend } else { 3154fd1ebb1SKevin Townsend g_assert_not_reached(); 3164fd1ebb1SKevin Townsend } 3174fd1ebb1SKevin Townsend 3184fd1ebb1SKevin Townsend return 0; 3194fd1ebb1SKevin Townsend } 3204fd1ebb1SKevin Townsend 3214fd1ebb1SKevin Townsend /* 3224fd1ebb1SKevin Townsend * Low-level slave-to-master transaction handler (read attempts). 3234fd1ebb1SKevin Townsend */ 3244fd1ebb1SKevin Townsend static uint8_t lsm303dlhc_mag_recv(I2CSlave *i2c) 3254fd1ebb1SKevin Townsend { 3264fd1ebb1SKevin Townsend LSM303DLHCMagState *s = LSM303DLHC_MAG(i2c); 3274fd1ebb1SKevin Townsend uint8_t resp; 3284fd1ebb1SKevin Townsend 3294fd1ebb1SKevin Townsend switch (s->pointer) { 3304fd1ebb1SKevin Townsend case LSM303DLHC_MAG_REG_CRA: 3314fd1ebb1SKevin Townsend resp = s->cra; 3324fd1ebb1SKevin Townsend break; 3334fd1ebb1SKevin Townsend case LSM303DLHC_MAG_REG_CRB: 3344fd1ebb1SKevin Townsend resp = s->crb; 3354fd1ebb1SKevin Townsend break; 3364fd1ebb1SKevin Townsend case LSM303DLHC_MAG_REG_MR: 3374fd1ebb1SKevin Townsend resp = s->mr; 3384fd1ebb1SKevin Townsend break; 3394fd1ebb1SKevin Townsend case LSM303DLHC_MAG_REG_OUT_X_H: 3404fd1ebb1SKevin Townsend resp = (uint8_t)(s->x_lock >> 8); 3414fd1ebb1SKevin Townsend break; 3424fd1ebb1SKevin Townsend case LSM303DLHC_MAG_REG_OUT_X_L: 3434fd1ebb1SKevin Townsend resp = (uint8_t)(s->x_lock); 3444fd1ebb1SKevin Townsend break; 3454fd1ebb1SKevin Townsend case LSM303DLHC_MAG_REG_OUT_Z_H: 3464fd1ebb1SKevin Townsend resp = (uint8_t)(s->z_lock >> 8); 3474fd1ebb1SKevin Townsend break; 3484fd1ebb1SKevin Townsend case LSM303DLHC_MAG_REG_OUT_Z_L: 3494fd1ebb1SKevin Townsend resp = (uint8_t)(s->z_lock); 3504fd1ebb1SKevin Townsend break; 3514fd1ebb1SKevin Townsend case LSM303DLHC_MAG_REG_OUT_Y_H: 3524fd1ebb1SKevin Townsend resp = (uint8_t)(s->y_lock >> 8); 3534fd1ebb1SKevin Townsend break; 3544fd1ebb1SKevin Townsend case LSM303DLHC_MAG_REG_OUT_Y_L: 3554fd1ebb1SKevin Townsend resp = (uint8_t)(s->y_lock); 3564fd1ebb1SKevin Townsend break; 3574fd1ebb1SKevin Townsend case LSM303DLHC_MAG_REG_SR: 3584fd1ebb1SKevin Townsend resp = s->sr; 3594fd1ebb1SKevin Townsend break; 3604fd1ebb1SKevin Townsend case LSM303DLHC_MAG_REG_IRA: 3614fd1ebb1SKevin Townsend resp = s->ira; 3624fd1ebb1SKevin Townsend break; 3634fd1ebb1SKevin Townsend case LSM303DLHC_MAG_REG_IRB: 3644fd1ebb1SKevin Townsend resp = s->irb; 3654fd1ebb1SKevin Townsend break; 3664fd1ebb1SKevin Townsend case LSM303DLHC_MAG_REG_IRC: 3674fd1ebb1SKevin Townsend resp = s->irc; 3684fd1ebb1SKevin Townsend break; 3694fd1ebb1SKevin Townsend case LSM303DLHC_MAG_REG_TEMP_OUT_H: 3704fd1ebb1SKevin Townsend /* Check if the temperature sensor is enabled or not (CRA & 0x80). */ 3714fd1ebb1SKevin Townsend if (s->cra & 0x80) { 3724fd1ebb1SKevin Townsend resp = (uint8_t)(s->temperature_lock >> 8); 3734fd1ebb1SKevin Townsend } else { 3744fd1ebb1SKevin Townsend resp = 0; 3754fd1ebb1SKevin Townsend } 3764fd1ebb1SKevin Townsend break; 3774fd1ebb1SKevin Townsend case LSM303DLHC_MAG_REG_TEMP_OUT_L: 3784fd1ebb1SKevin Townsend if (s->cra & 0x80) { 3794fd1ebb1SKevin Townsend resp = (uint8_t)(s->temperature_lock & 0xff); 3804fd1ebb1SKevin Townsend } else { 3814fd1ebb1SKevin Townsend resp = 0; 3824fd1ebb1SKevin Townsend } 3834fd1ebb1SKevin Townsend break; 3844fd1ebb1SKevin Townsend default: 3854fd1ebb1SKevin Townsend resp = 0; 3864fd1ebb1SKevin Townsend break; 3874fd1ebb1SKevin Townsend } 3884fd1ebb1SKevin Townsend 3894fd1ebb1SKevin Townsend /* 3904fd1ebb1SKevin Townsend * The address pointer on the LSM303DLHC auto-increments whenever a byte 3914fd1ebb1SKevin Townsend * is read, without the master device having to request the next address. 3924fd1ebb1SKevin Townsend * 3934fd1ebb1SKevin Townsend * The auto-increment process has the following logic: 3944fd1ebb1SKevin Townsend * 3954fd1ebb1SKevin Townsend * - if (s->pointer == 8) then s->pointer = 3 3964fd1ebb1SKevin Townsend * - else: if (s->pointer == 12) then s->pointer = 0 3974fd1ebb1SKevin Townsend * - else: s->pointer += 1 3984fd1ebb1SKevin Townsend * 3994fd1ebb1SKevin Townsend * Reading an invalid address return 0. 4004fd1ebb1SKevin Townsend */ 4014fd1ebb1SKevin Townsend if (s->pointer == LSM303DLHC_MAG_REG_OUT_Y_L) { 4024fd1ebb1SKevin Townsend s->pointer = LSM303DLHC_MAG_REG_OUT_X_H; 4034fd1ebb1SKevin Townsend } else if (s->pointer == LSM303DLHC_MAG_REG_IRC) { 4044fd1ebb1SKevin Townsend s->pointer = LSM303DLHC_MAG_REG_CRA; 4054fd1ebb1SKevin Townsend } else { 4064fd1ebb1SKevin Townsend s->pointer++; 4074fd1ebb1SKevin Townsend } 4084fd1ebb1SKevin Townsend 4094fd1ebb1SKevin Townsend return resp; 4104fd1ebb1SKevin Townsend } 4114fd1ebb1SKevin Townsend 4124fd1ebb1SKevin Townsend /* 4134fd1ebb1SKevin Townsend * Bus state change handler. 4144fd1ebb1SKevin Townsend */ 4154fd1ebb1SKevin Townsend static int lsm303dlhc_mag_event(I2CSlave *i2c, enum i2c_event event) 4164fd1ebb1SKevin Townsend { 4174fd1ebb1SKevin Townsend LSM303DLHCMagState *s = LSM303DLHC_MAG(i2c); 4184fd1ebb1SKevin Townsend 4194fd1ebb1SKevin Townsend switch (event) { 4204fd1ebb1SKevin Townsend case I2C_START_SEND: 4214fd1ebb1SKevin Townsend break; 4224fd1ebb1SKevin Townsend case I2C_START_RECV: 4234fd1ebb1SKevin Townsend lsm303dlhc_mag_read(s); 4244fd1ebb1SKevin Townsend break; 4254fd1ebb1SKevin Townsend case I2C_FINISH: 4264fd1ebb1SKevin Townsend lsm303dlhc_mag_finish(s); 4274fd1ebb1SKevin Townsend break; 4284fd1ebb1SKevin Townsend case I2C_NACK: 4294fd1ebb1SKevin Townsend break; 430a78e9839SKlaus Jensen default: 431a78e9839SKlaus Jensen return -1; 4324fd1ebb1SKevin Townsend } 4334fd1ebb1SKevin Townsend 4344fd1ebb1SKevin Townsend s->len = 0; 4354fd1ebb1SKevin Townsend return 0; 4364fd1ebb1SKevin Townsend } 4374fd1ebb1SKevin Townsend 4384fd1ebb1SKevin Townsend /* 4394fd1ebb1SKevin Townsend * Device data description using VMSTATE macros. 4404fd1ebb1SKevin Townsend */ 4414fd1ebb1SKevin Townsend static const VMStateDescription vmstate_lsm303dlhc_mag = { 4424fd1ebb1SKevin Townsend .name = "LSM303DLHC_MAG", 4434fd1ebb1SKevin Townsend .version_id = 0, 4444fd1ebb1SKevin Townsend .minimum_version_id = 0, 445af10fff2SRichard Henderson .fields = (const VMStateField[]) { 4464fd1ebb1SKevin Townsend 4474fd1ebb1SKevin Townsend VMSTATE_I2C_SLAVE(parent_obj, LSM303DLHCMagState), 4484fd1ebb1SKevin Townsend VMSTATE_UINT8(len, LSM303DLHCMagState), 4494fd1ebb1SKevin Townsend VMSTATE_UINT8(buf, LSM303DLHCMagState), 4504fd1ebb1SKevin Townsend VMSTATE_UINT8(pointer, LSM303DLHCMagState), 4514fd1ebb1SKevin Townsend VMSTATE_UINT8(cra, LSM303DLHCMagState), 4524fd1ebb1SKevin Townsend VMSTATE_UINT8(crb, LSM303DLHCMagState), 4534fd1ebb1SKevin Townsend VMSTATE_UINT8(mr, LSM303DLHCMagState), 4544fd1ebb1SKevin Townsend VMSTATE_INT16(x, LSM303DLHCMagState), 4554fd1ebb1SKevin Townsend VMSTATE_INT16(z, LSM303DLHCMagState), 4564fd1ebb1SKevin Townsend VMSTATE_INT16(y, LSM303DLHCMagState), 4574fd1ebb1SKevin Townsend VMSTATE_INT16(x_lock, LSM303DLHCMagState), 4584fd1ebb1SKevin Townsend VMSTATE_INT16(z_lock, LSM303DLHCMagState), 4594fd1ebb1SKevin Townsend VMSTATE_INT16(y_lock, LSM303DLHCMagState), 4604fd1ebb1SKevin Townsend VMSTATE_UINT8(sr, LSM303DLHCMagState), 4614fd1ebb1SKevin Townsend VMSTATE_UINT8(ira, LSM303DLHCMagState), 4624fd1ebb1SKevin Townsend VMSTATE_UINT8(irb, LSM303DLHCMagState), 4634fd1ebb1SKevin Townsend VMSTATE_UINT8(irc, LSM303DLHCMagState), 4644fd1ebb1SKevin Townsend VMSTATE_INT16(temperature, LSM303DLHCMagState), 4654fd1ebb1SKevin Townsend VMSTATE_INT16(temperature_lock, LSM303DLHCMagState), 4664fd1ebb1SKevin Townsend VMSTATE_END_OF_LIST() 4674fd1ebb1SKevin Townsend } 4684fd1ebb1SKevin Townsend }; 4694fd1ebb1SKevin Townsend 4704fd1ebb1SKevin Townsend /* 4714fd1ebb1SKevin Townsend * Put the device into post-reset default state. 4724fd1ebb1SKevin Townsend */ 4734fd1ebb1SKevin Townsend static void lsm303dlhc_mag_default_cfg(LSM303DLHCMagState *s) 4744fd1ebb1SKevin Townsend { 4754fd1ebb1SKevin Townsend /* Set the device into is default reset state. */ 4764fd1ebb1SKevin Townsend s->len = 0; 4774fd1ebb1SKevin Townsend s->pointer = 0; /* Current register. */ 4784fd1ebb1SKevin Townsend s->buf = 0; /* Shared buffer. */ 4794fd1ebb1SKevin Townsend s->cra = 0x10; /* Temp Enabled = 0, Data Rate = 15.0 Hz. */ 4804fd1ebb1SKevin Townsend s->crb = 0x20; /* Gain = +/- 1.3 Gauss. */ 4814fd1ebb1SKevin Townsend s->mr = 0x3; /* Operating Mode = Sleep. */ 4824fd1ebb1SKevin Townsend s->x = 0; 4834fd1ebb1SKevin Townsend s->z = 0; 4844fd1ebb1SKevin Townsend s->y = 0; 4854fd1ebb1SKevin Townsend s->x_lock = 0; 4864fd1ebb1SKevin Townsend s->z_lock = 0; 4874fd1ebb1SKevin Townsend s->y_lock = 0; 4884fd1ebb1SKevin Townsend s->sr = 0x1; /* DRDY = 1. */ 4894fd1ebb1SKevin Townsend s->ira = 0x48; 4904fd1ebb1SKevin Townsend s->irb = 0x34; 4914fd1ebb1SKevin Townsend s->irc = 0x33; 4924fd1ebb1SKevin Townsend s->temperature = 0; /* Default to 0 degrees C (0/8 lsb = 0 C). */ 4934fd1ebb1SKevin Townsend s->temperature_lock = 0; 4944fd1ebb1SKevin Townsend } 4954fd1ebb1SKevin Townsend 4964fd1ebb1SKevin Townsend /* 4974fd1ebb1SKevin Townsend * Callback handler when DeviceState 'reset' is set to true. 4984fd1ebb1SKevin Townsend */ 4994fd1ebb1SKevin Townsend static void lsm303dlhc_mag_reset(DeviceState *dev) 5004fd1ebb1SKevin Townsend { 5014fd1ebb1SKevin Townsend I2CSlave *i2c = I2C_SLAVE(dev); 5024fd1ebb1SKevin Townsend LSM303DLHCMagState *s = LSM303DLHC_MAG(i2c); 5034fd1ebb1SKevin Townsend 5044fd1ebb1SKevin Townsend /* Set the device into its default reset state. */ 5054fd1ebb1SKevin Townsend lsm303dlhc_mag_default_cfg(s); 5064fd1ebb1SKevin Townsend } 5074fd1ebb1SKevin Townsend 5084fd1ebb1SKevin Townsend /* 5094fd1ebb1SKevin Townsend * Initialisation of any public properties. 5104fd1ebb1SKevin Townsend */ 5114fd1ebb1SKevin Townsend static void lsm303dlhc_mag_initfn(Object *obj) 5124fd1ebb1SKevin Townsend { 5134fd1ebb1SKevin Townsend object_property_add(obj, "mag-x", "int", 5144fd1ebb1SKevin Townsend lsm303dlhc_mag_get_x, 5154fd1ebb1SKevin Townsend lsm303dlhc_mag_set_x, NULL, NULL); 5164fd1ebb1SKevin Townsend 5174fd1ebb1SKevin Townsend object_property_add(obj, "mag-y", "int", 5184fd1ebb1SKevin Townsend lsm303dlhc_mag_get_y, 5194fd1ebb1SKevin Townsend lsm303dlhc_mag_set_y, NULL, NULL); 5204fd1ebb1SKevin Townsend 5214fd1ebb1SKevin Townsend object_property_add(obj, "mag-z", "int", 5224fd1ebb1SKevin Townsend lsm303dlhc_mag_get_z, 5234fd1ebb1SKevin Townsend lsm303dlhc_mag_set_z, NULL, NULL); 5244fd1ebb1SKevin Townsend 5254fd1ebb1SKevin Townsend object_property_add(obj, "temperature", "int", 5264fd1ebb1SKevin Townsend lsm303dlhc_mag_get_temperature, 5274fd1ebb1SKevin Townsend lsm303dlhc_mag_set_temperature, NULL, NULL); 5284fd1ebb1SKevin Townsend } 5294fd1ebb1SKevin Townsend 5304fd1ebb1SKevin Townsend /* 5314fd1ebb1SKevin Townsend * Set the virtual method pointers (bus state change, tx/rx, etc.). 5324fd1ebb1SKevin Townsend */ 5334fd1ebb1SKevin Townsend static void lsm303dlhc_mag_class_init(ObjectClass *klass, void *data) 5344fd1ebb1SKevin Townsend { 5354fd1ebb1SKevin Townsend DeviceClass *dc = DEVICE_CLASS(klass); 5364fd1ebb1SKevin Townsend I2CSlaveClass *k = I2C_SLAVE_CLASS(klass); 5374fd1ebb1SKevin Townsend 538*e3d08143SPeter Maydell device_class_set_legacy_reset(dc, lsm303dlhc_mag_reset); 5394fd1ebb1SKevin Townsend dc->vmsd = &vmstate_lsm303dlhc_mag; 5404fd1ebb1SKevin Townsend k->event = lsm303dlhc_mag_event; 5414fd1ebb1SKevin Townsend k->recv = lsm303dlhc_mag_recv; 5424fd1ebb1SKevin Townsend k->send = lsm303dlhc_mag_send; 5434fd1ebb1SKevin Townsend } 5444fd1ebb1SKevin Townsend 5454fd1ebb1SKevin Townsend static const TypeInfo lsm303dlhc_mag_info = { 5464fd1ebb1SKevin Townsend .name = TYPE_LSM303DLHC_MAG, 5474fd1ebb1SKevin Townsend .parent = TYPE_I2C_SLAVE, 5484fd1ebb1SKevin Townsend .instance_size = sizeof(LSM303DLHCMagState), 5494fd1ebb1SKevin Townsend .instance_init = lsm303dlhc_mag_initfn, 5504fd1ebb1SKevin Townsend .class_init = lsm303dlhc_mag_class_init, 5514fd1ebb1SKevin Townsend }; 5524fd1ebb1SKevin Townsend 5534fd1ebb1SKevin Townsend static void lsm303dlhc_mag_register_types(void) 5544fd1ebb1SKevin Townsend { 5554fd1ebb1SKevin Townsend type_register_static(&lsm303dlhc_mag_info); 5564fd1ebb1SKevin Townsend } 5574fd1ebb1SKevin Townsend 5584fd1ebb1SKevin Townsend type_init(lsm303dlhc_mag_register_types) 559