xref: /qemu/hw/sensor/lsm303dlhc_mag.c (revision af10fff2)
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 
lsm303dlhc_mag_get_x(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)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 
lsm303dlhc_mag_get_y(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)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 
lsm303dlhc_mag_get_z(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)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 
lsm303dlhc_mag_set_x(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)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 
lsm303dlhc_mag_set_y(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)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 
lsm303dlhc_mag_set_z(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)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  */
lsm303dlhc_mag_get_temperature(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)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  */
lsm303dlhc_mag_set_temperature(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)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  */
lsm303dlhc_mag_read(LSM303DLHCMagState * s)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  */
lsm303dlhc_mag_finish(LSM303DLHCMagState * s)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  */
lsm303dlhc_mag_write(LSM303DLHCMagState * s)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  */
lsm303dlhc_mag_send(I2CSlave * i2c,uint8_t data)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  */
lsm303dlhc_mag_recv(I2CSlave * i2c)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  */
lsm303dlhc_mag_event(I2CSlave * i2c,enum i2c_event event)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,
445*af10fff2SRichard 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  */
lsm303dlhc_mag_default_cfg(LSM303DLHCMagState * s)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  */
lsm303dlhc_mag_reset(DeviceState * dev)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  */
lsm303dlhc_mag_initfn(Object * obj)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  */
lsm303dlhc_mag_class_init(ObjectClass * klass,void * data)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 
5384fd1ebb1SKevin Townsend     dc->reset = 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 
lsm303dlhc_mag_register_types(void)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