xref: /qemu/hw/sensor/lsm303dlhc_mag.c (revision 4fd1ebb10593087d45d2f56f7f3d13447d24802c)
1*4fd1ebb1SKevin Townsend /*
2*4fd1ebb1SKevin Townsend  * LSM303DLHC I2C magnetometer.
3*4fd1ebb1SKevin Townsend  *
4*4fd1ebb1SKevin Townsend  * Copyright (C) 2021 Linaro Ltd.
5*4fd1ebb1SKevin Townsend  * Written by Kevin Townsend <kevin.townsend@linaro.org>
6*4fd1ebb1SKevin Townsend  *
7*4fd1ebb1SKevin Townsend  * Based on: https://www.st.com/resource/en/datasheet/lsm303dlhc.pdf
8*4fd1ebb1SKevin Townsend  *
9*4fd1ebb1SKevin Townsend  * SPDX-License-Identifier: GPL-2.0-or-later
10*4fd1ebb1SKevin Townsend  */
11*4fd1ebb1SKevin Townsend 
12*4fd1ebb1SKevin Townsend /*
13*4fd1ebb1SKevin Townsend  * The I2C address associated with this device is set on the command-line when
14*4fd1ebb1SKevin Townsend  * initialising the machine, but the following address is standard: 0x1E.
15*4fd1ebb1SKevin Townsend  *
16*4fd1ebb1SKevin Townsend  * Get and set functions for 'mag-x', 'mag-y' and 'mag-z' assume that
17*4fd1ebb1SKevin Townsend  * 1 = 0.001 uT. (NOTE the 1 gauss = 100 uT, so setting a value of 100,000
18*4fd1ebb1SKevin Townsend  * would be equal to 1 gauss or 100 uT.)
19*4fd1ebb1SKevin Townsend  *
20*4fd1ebb1SKevin Townsend  * Get and set functions for 'temperature' assume that 1 = 0.001 C, so 23.6 C
21*4fd1ebb1SKevin Townsend  * would be equal to 23600.
22*4fd1ebb1SKevin Townsend  */
23*4fd1ebb1SKevin Townsend 
24*4fd1ebb1SKevin Townsend #include "qemu/osdep.h"
25*4fd1ebb1SKevin Townsend #include "hw/i2c/i2c.h"
26*4fd1ebb1SKevin Townsend #include "migration/vmstate.h"
27*4fd1ebb1SKevin Townsend #include "qapi/error.h"
28*4fd1ebb1SKevin Townsend #include "qapi/visitor.h"
29*4fd1ebb1SKevin Townsend #include "qemu/module.h"
30*4fd1ebb1SKevin Townsend #include "qemu/log.h"
31*4fd1ebb1SKevin Townsend #include "qemu/bswap.h"
32*4fd1ebb1SKevin Townsend 
33*4fd1ebb1SKevin Townsend enum LSM303DLHCMagReg {
34*4fd1ebb1SKevin Townsend     LSM303DLHC_MAG_REG_CRA          = 0x00,
35*4fd1ebb1SKevin Townsend     LSM303DLHC_MAG_REG_CRB          = 0x01,
36*4fd1ebb1SKevin Townsend     LSM303DLHC_MAG_REG_MR           = 0x02,
37*4fd1ebb1SKevin Townsend     LSM303DLHC_MAG_REG_OUT_X_H      = 0x03,
38*4fd1ebb1SKevin Townsend     LSM303DLHC_MAG_REG_OUT_X_L      = 0x04,
39*4fd1ebb1SKevin Townsend     LSM303DLHC_MAG_REG_OUT_Z_H      = 0x05,
40*4fd1ebb1SKevin Townsend     LSM303DLHC_MAG_REG_OUT_Z_L      = 0x06,
41*4fd1ebb1SKevin Townsend     LSM303DLHC_MAG_REG_OUT_Y_H      = 0x07,
42*4fd1ebb1SKevin Townsend     LSM303DLHC_MAG_REG_OUT_Y_L      = 0x08,
43*4fd1ebb1SKevin Townsend     LSM303DLHC_MAG_REG_SR           = 0x09,
44*4fd1ebb1SKevin Townsend     LSM303DLHC_MAG_REG_IRA          = 0x0A,
45*4fd1ebb1SKevin Townsend     LSM303DLHC_MAG_REG_IRB          = 0x0B,
46*4fd1ebb1SKevin Townsend     LSM303DLHC_MAG_REG_IRC          = 0x0C,
47*4fd1ebb1SKevin Townsend     LSM303DLHC_MAG_REG_TEMP_OUT_H   = 0x31,
48*4fd1ebb1SKevin Townsend     LSM303DLHC_MAG_REG_TEMP_OUT_L   = 0x32
49*4fd1ebb1SKevin Townsend };
50*4fd1ebb1SKevin Townsend 
51*4fd1ebb1SKevin Townsend typedef struct LSM303DLHCMagState {
52*4fd1ebb1SKevin Townsend     I2CSlave parent_obj;
53*4fd1ebb1SKevin Townsend     uint8_t cra;
54*4fd1ebb1SKevin Townsend     uint8_t crb;
55*4fd1ebb1SKevin Townsend     uint8_t mr;
56*4fd1ebb1SKevin Townsend     int16_t x;
57*4fd1ebb1SKevin Townsend     int16_t z;
58*4fd1ebb1SKevin Townsend     int16_t y;
59*4fd1ebb1SKevin Townsend     int16_t x_lock;
60*4fd1ebb1SKevin Townsend     int16_t z_lock;
61*4fd1ebb1SKevin Townsend     int16_t y_lock;
62*4fd1ebb1SKevin Townsend     uint8_t sr;
63*4fd1ebb1SKevin Townsend     uint8_t ira;
64*4fd1ebb1SKevin Townsend     uint8_t irb;
65*4fd1ebb1SKevin Townsend     uint8_t irc;
66*4fd1ebb1SKevin Townsend     int16_t temperature;
67*4fd1ebb1SKevin Townsend     int16_t temperature_lock;
68*4fd1ebb1SKevin Townsend     uint8_t len;
69*4fd1ebb1SKevin Townsend     uint8_t buf;
70*4fd1ebb1SKevin Townsend     uint8_t pointer;
71*4fd1ebb1SKevin Townsend } LSM303DLHCMagState;
72*4fd1ebb1SKevin Townsend 
73*4fd1ebb1SKevin Townsend #define TYPE_LSM303DLHC_MAG "lsm303dlhc_mag"
74*4fd1ebb1SKevin Townsend OBJECT_DECLARE_SIMPLE_TYPE(LSM303DLHCMagState, LSM303DLHC_MAG)
75*4fd1ebb1SKevin Townsend 
76*4fd1ebb1SKevin Townsend /*
77*4fd1ebb1SKevin Townsend  * Conversion factor from Gauss to sensor values for each GN gain setting,
78*4fd1ebb1SKevin Townsend  * in units "lsb per Gauss" (see data sheet table 3). There is no documented
79*4fd1ebb1SKevin Townsend  * behaviour if the GN setting in CRB is incorrectly set to 0b000;
80*4fd1ebb1SKevin Townsend  * we arbitrarily make it the same as 0b001.
81*4fd1ebb1SKevin Townsend  */
82*4fd1ebb1SKevin Townsend uint32_t xy_gain[] = { 1100, 1100, 855, 670, 450, 400, 330, 230 };
83*4fd1ebb1SKevin Townsend uint32_t z_gain[] = { 980, 980, 760, 600, 400, 355, 295, 205 };
84*4fd1ebb1SKevin Townsend 
85*4fd1ebb1SKevin Townsend static void lsm303dlhc_mag_get_x(Object *obj, Visitor *v, const char *name,
86*4fd1ebb1SKevin Townsend                                  void *opaque, Error **errp)
87*4fd1ebb1SKevin Townsend {
88*4fd1ebb1SKevin Townsend     LSM303DLHCMagState *s = LSM303DLHC_MAG(obj);
89*4fd1ebb1SKevin Townsend     int gm = extract32(s->crb, 5, 3);
90*4fd1ebb1SKevin Townsend 
91*4fd1ebb1SKevin Townsend     /* Convert to uT where 1000 = 1 uT. Conversion factor depends on gain. */
92*4fd1ebb1SKevin Townsend     int64_t value = muldiv64(s->x, 100000, xy_gain[gm]);
93*4fd1ebb1SKevin Townsend     visit_type_int(v, name, &value, errp);
94*4fd1ebb1SKevin Townsend }
95*4fd1ebb1SKevin Townsend 
96*4fd1ebb1SKevin Townsend static void lsm303dlhc_mag_get_y(Object *obj, Visitor *v, const char *name,
97*4fd1ebb1SKevin Townsend                                  void *opaque, Error **errp)
98*4fd1ebb1SKevin Townsend {
99*4fd1ebb1SKevin Townsend     LSM303DLHCMagState *s = LSM303DLHC_MAG(obj);
100*4fd1ebb1SKevin Townsend     int gm = extract32(s->crb, 5, 3);
101*4fd1ebb1SKevin Townsend 
102*4fd1ebb1SKevin Townsend     /* Convert to uT where 1000 = 1 uT. Conversion factor depends on gain. */
103*4fd1ebb1SKevin Townsend     int64_t value = muldiv64(s->y, 100000, xy_gain[gm]);
104*4fd1ebb1SKevin Townsend     visit_type_int(v, name, &value, errp);
105*4fd1ebb1SKevin Townsend }
106*4fd1ebb1SKevin Townsend 
107*4fd1ebb1SKevin Townsend static void lsm303dlhc_mag_get_z(Object *obj, Visitor *v, const char *name,
108*4fd1ebb1SKevin Townsend                                  void *opaque, Error **errp)
109*4fd1ebb1SKevin Townsend {
110*4fd1ebb1SKevin Townsend     LSM303DLHCMagState *s = LSM303DLHC_MAG(obj);
111*4fd1ebb1SKevin Townsend     int gm = extract32(s->crb, 5, 3);
112*4fd1ebb1SKevin Townsend 
113*4fd1ebb1SKevin Townsend     /* Convert to uT where 1000 = 1 uT. Conversion factor depends on gain. */
114*4fd1ebb1SKevin Townsend     int64_t value = muldiv64(s->z, 100000, z_gain[gm]);
115*4fd1ebb1SKevin Townsend     visit_type_int(v, name, &value, errp);
116*4fd1ebb1SKevin Townsend }
117*4fd1ebb1SKevin Townsend 
118*4fd1ebb1SKevin Townsend static void lsm303dlhc_mag_set_x(Object *obj, Visitor *v, const char *name,
119*4fd1ebb1SKevin Townsend                                  void *opaque, Error **errp)
120*4fd1ebb1SKevin Townsend {
121*4fd1ebb1SKevin Townsend     LSM303DLHCMagState *s = LSM303DLHC_MAG(obj);
122*4fd1ebb1SKevin Townsend     int64_t value;
123*4fd1ebb1SKevin Townsend     int64_t reg;
124*4fd1ebb1SKevin Townsend     int gm = extract32(s->crb, 5, 3);
125*4fd1ebb1SKevin Townsend 
126*4fd1ebb1SKevin Townsend     if (!visit_type_int(v, name, &value, errp)) {
127*4fd1ebb1SKevin Townsend         return;
128*4fd1ebb1SKevin Townsend     }
129*4fd1ebb1SKevin Townsend 
130*4fd1ebb1SKevin Townsend     reg = muldiv64(value, xy_gain[gm], 100000);
131*4fd1ebb1SKevin Townsend 
132*4fd1ebb1SKevin Townsend     /* Make sure we are within a 12-bit limit. */
133*4fd1ebb1SKevin Townsend     if (reg > 2047 || reg < -2048) {
134*4fd1ebb1SKevin Townsend         error_setg(errp, "value %" PRId64 " out of register's range", value);
135*4fd1ebb1SKevin Townsend         return;
136*4fd1ebb1SKevin Townsend     }
137*4fd1ebb1SKevin Townsend 
138*4fd1ebb1SKevin Townsend     s->x = (int16_t)reg;
139*4fd1ebb1SKevin Townsend }
140*4fd1ebb1SKevin Townsend 
141*4fd1ebb1SKevin Townsend static void lsm303dlhc_mag_set_y(Object *obj, Visitor *v, const char *name,
142*4fd1ebb1SKevin Townsend                                  void *opaque, Error **errp)
143*4fd1ebb1SKevin Townsend {
144*4fd1ebb1SKevin Townsend     LSM303DLHCMagState *s = LSM303DLHC_MAG(obj);
145*4fd1ebb1SKevin Townsend     int64_t value;
146*4fd1ebb1SKevin Townsend     int64_t reg;
147*4fd1ebb1SKevin Townsend     int gm = extract32(s->crb, 5, 3);
148*4fd1ebb1SKevin Townsend 
149*4fd1ebb1SKevin Townsend     if (!visit_type_int(v, name, &value, errp)) {
150*4fd1ebb1SKevin Townsend         return;
151*4fd1ebb1SKevin Townsend     }
152*4fd1ebb1SKevin Townsend 
153*4fd1ebb1SKevin Townsend     reg = muldiv64(value, xy_gain[gm], 100000);
154*4fd1ebb1SKevin Townsend 
155*4fd1ebb1SKevin Townsend     /* Make sure we are within a 12-bit limit. */
156*4fd1ebb1SKevin Townsend     if (reg > 2047 || reg < -2048) {
157*4fd1ebb1SKevin Townsend         error_setg(errp, "value %" PRId64 " out of register's range", value);
158*4fd1ebb1SKevin Townsend         return;
159*4fd1ebb1SKevin Townsend     }
160*4fd1ebb1SKevin Townsend 
161*4fd1ebb1SKevin Townsend     s->y = (int16_t)reg;
162*4fd1ebb1SKevin Townsend }
163*4fd1ebb1SKevin Townsend 
164*4fd1ebb1SKevin Townsend static void lsm303dlhc_mag_set_z(Object *obj, Visitor *v, const char *name,
165*4fd1ebb1SKevin Townsend                                  void *opaque, Error **errp)
166*4fd1ebb1SKevin Townsend {
167*4fd1ebb1SKevin Townsend     LSM303DLHCMagState *s = LSM303DLHC_MAG(obj);
168*4fd1ebb1SKevin Townsend     int64_t value;
169*4fd1ebb1SKevin Townsend     int64_t reg;
170*4fd1ebb1SKevin Townsend     int gm = extract32(s->crb, 5, 3);
171*4fd1ebb1SKevin Townsend 
172*4fd1ebb1SKevin Townsend     if (!visit_type_int(v, name, &value, errp)) {
173*4fd1ebb1SKevin Townsend         return;
174*4fd1ebb1SKevin Townsend     }
175*4fd1ebb1SKevin Townsend 
176*4fd1ebb1SKevin Townsend     reg = muldiv64(value, z_gain[gm], 100000);
177*4fd1ebb1SKevin Townsend 
178*4fd1ebb1SKevin Townsend     /* Make sure we are within a 12-bit limit. */
179*4fd1ebb1SKevin Townsend     if (reg > 2047 || reg < -2048) {
180*4fd1ebb1SKevin Townsend         error_setg(errp, "value %" PRId64 " out of register's range", value);
181*4fd1ebb1SKevin Townsend         return;
182*4fd1ebb1SKevin Townsend     }
183*4fd1ebb1SKevin Townsend 
184*4fd1ebb1SKevin Townsend     s->z = (int16_t)reg;
185*4fd1ebb1SKevin Townsend }
186*4fd1ebb1SKevin Townsend 
187*4fd1ebb1SKevin Townsend /*
188*4fd1ebb1SKevin Townsend  * Get handler for the temperature property.
189*4fd1ebb1SKevin Townsend  */
190*4fd1ebb1SKevin Townsend static void lsm303dlhc_mag_get_temperature(Object *obj, Visitor *v,
191*4fd1ebb1SKevin Townsend                                            const char *name, void *opaque,
192*4fd1ebb1SKevin Townsend                                            Error **errp)
193*4fd1ebb1SKevin Townsend {
194*4fd1ebb1SKevin Townsend     LSM303DLHCMagState *s = LSM303DLHC_MAG(obj);
195*4fd1ebb1SKevin Townsend     int64_t value;
196*4fd1ebb1SKevin Townsend 
197*4fd1ebb1SKevin Townsend     /* Convert to 1 lsb = 0.125 C to 1 = 0.001 C for 'temperature' property. */
198*4fd1ebb1SKevin Townsend     value = s->temperature * 125;
199*4fd1ebb1SKevin Townsend 
200*4fd1ebb1SKevin Townsend     visit_type_int(v, name, &value, errp);
201*4fd1ebb1SKevin Townsend }
202*4fd1ebb1SKevin Townsend 
203*4fd1ebb1SKevin Townsend /*
204*4fd1ebb1SKevin Townsend  * Set handler for the temperature property.
205*4fd1ebb1SKevin Townsend  */
206*4fd1ebb1SKevin Townsend static void lsm303dlhc_mag_set_temperature(Object *obj, Visitor *v,
207*4fd1ebb1SKevin Townsend                                            const char *name, void *opaque,
208*4fd1ebb1SKevin Townsend                                            Error **errp)
209*4fd1ebb1SKevin Townsend {
210*4fd1ebb1SKevin Townsend     LSM303DLHCMagState *s = LSM303DLHC_MAG(obj);
211*4fd1ebb1SKevin Townsend     int64_t value;
212*4fd1ebb1SKevin Townsend 
213*4fd1ebb1SKevin Townsend     if (!visit_type_int(v, name, &value, errp)) {
214*4fd1ebb1SKevin Townsend         return;
215*4fd1ebb1SKevin Townsend     }
216*4fd1ebb1SKevin Townsend 
217*4fd1ebb1SKevin Townsend     /* Input temperature is in 0.001 C units. Convert to 1 lsb = 0.125 C. */
218*4fd1ebb1SKevin Townsend     value /= 125;
219*4fd1ebb1SKevin Townsend 
220*4fd1ebb1SKevin Townsend     if (value > 2047 || value < -2048) {
221*4fd1ebb1SKevin Townsend         error_setg(errp, "value %" PRId64 " lsb is out of range", value);
222*4fd1ebb1SKevin Townsend         return;
223*4fd1ebb1SKevin Townsend     }
224*4fd1ebb1SKevin Townsend 
225*4fd1ebb1SKevin Townsend     s->temperature = (int16_t)value;
226*4fd1ebb1SKevin Townsend }
227*4fd1ebb1SKevin Townsend 
228*4fd1ebb1SKevin Townsend /*
229*4fd1ebb1SKevin Townsend  * Callback handler whenever a 'I2C_START_RECV' (read) event is received.
230*4fd1ebb1SKevin Townsend  */
231*4fd1ebb1SKevin Townsend static void lsm303dlhc_mag_read(LSM303DLHCMagState *s)
232*4fd1ebb1SKevin Townsend {
233*4fd1ebb1SKevin Townsend     /*
234*4fd1ebb1SKevin Townsend      * Set the LOCK bit whenever a new read attempt is made. This will be
235*4fd1ebb1SKevin Townsend      * cleared in I2C_FINISH. Note that DRDY is always set to 1 in this driver.
236*4fd1ebb1SKevin Townsend      */
237*4fd1ebb1SKevin Townsend     s->sr = 0x3;
238*4fd1ebb1SKevin Townsend 
239*4fd1ebb1SKevin Townsend     /*
240*4fd1ebb1SKevin Townsend      * Copy the current X/Y/Z and temp. values into the locked registers so
241*4fd1ebb1SKevin Townsend      * that 'mag-x', 'mag-y', 'mag-z' and 'temperature' can continue to be
242*4fd1ebb1SKevin Townsend      * updated via QOM, etc., without corrupting the current read event.
243*4fd1ebb1SKevin Townsend      */
244*4fd1ebb1SKevin Townsend     s->x_lock = s->x;
245*4fd1ebb1SKevin Townsend     s->z_lock = s->z;
246*4fd1ebb1SKevin Townsend     s->y_lock = s->y;
247*4fd1ebb1SKevin Townsend     s->temperature_lock = s->temperature;
248*4fd1ebb1SKevin Townsend }
249*4fd1ebb1SKevin Townsend 
250*4fd1ebb1SKevin Townsend /*
251*4fd1ebb1SKevin Townsend  * Callback handler whenever a 'I2C_FINISH' event is received.
252*4fd1ebb1SKevin Townsend  */
253*4fd1ebb1SKevin Townsend static void lsm303dlhc_mag_finish(LSM303DLHCMagState *s)
254*4fd1ebb1SKevin Townsend {
255*4fd1ebb1SKevin Townsend     /*
256*4fd1ebb1SKevin Townsend      * Clear the LOCK bit when the read attempt terminates.
257*4fd1ebb1SKevin Townsend      * This bit is initially set in the I2C_START_RECV handler.
258*4fd1ebb1SKevin Townsend      */
259*4fd1ebb1SKevin Townsend     s->sr = 0x1;
260*4fd1ebb1SKevin Townsend }
261*4fd1ebb1SKevin Townsend 
262*4fd1ebb1SKevin Townsend /*
263*4fd1ebb1SKevin Townsend  * Callback handler when a device attempts to write to a register.
264*4fd1ebb1SKevin Townsend  */
265*4fd1ebb1SKevin Townsend static void lsm303dlhc_mag_write(LSM303DLHCMagState *s)
266*4fd1ebb1SKevin Townsend {
267*4fd1ebb1SKevin Townsend     switch (s->pointer) {
268*4fd1ebb1SKevin Townsend     case LSM303DLHC_MAG_REG_CRA:
269*4fd1ebb1SKevin Townsend         s->cra = s->buf;
270*4fd1ebb1SKevin Townsend         break;
271*4fd1ebb1SKevin Townsend     case LSM303DLHC_MAG_REG_CRB:
272*4fd1ebb1SKevin Townsend         /* Make sure gain is at least 1, falling back to 1 on an error. */
273*4fd1ebb1SKevin Townsend         if (s->buf >> 5 == 0) {
274*4fd1ebb1SKevin Townsend             s->buf = 1 << 5;
275*4fd1ebb1SKevin Townsend         }
276*4fd1ebb1SKevin Townsend         s->crb = s->buf;
277*4fd1ebb1SKevin Townsend         break;
278*4fd1ebb1SKevin Townsend     case LSM303DLHC_MAG_REG_MR:
279*4fd1ebb1SKevin Townsend         s->mr = s->buf;
280*4fd1ebb1SKevin Townsend         break;
281*4fd1ebb1SKevin Townsend     case LSM303DLHC_MAG_REG_SR:
282*4fd1ebb1SKevin Townsend         s->sr = s->buf;
283*4fd1ebb1SKevin Townsend         break;
284*4fd1ebb1SKevin Townsend     case LSM303DLHC_MAG_REG_IRA:
285*4fd1ebb1SKevin Townsend         s->ira = s->buf;
286*4fd1ebb1SKevin Townsend         break;
287*4fd1ebb1SKevin Townsend     case LSM303DLHC_MAG_REG_IRB:
288*4fd1ebb1SKevin Townsend         s->irb = s->buf;
289*4fd1ebb1SKevin Townsend         break;
290*4fd1ebb1SKevin Townsend     case LSM303DLHC_MAG_REG_IRC:
291*4fd1ebb1SKevin Townsend         s->irc = s->buf;
292*4fd1ebb1SKevin Townsend         break;
293*4fd1ebb1SKevin Townsend     default:
294*4fd1ebb1SKevin Townsend         qemu_log_mask(LOG_GUEST_ERROR, "reg is read-only: 0x%02X", s->buf);
295*4fd1ebb1SKevin Townsend         break;
296*4fd1ebb1SKevin Townsend     }
297*4fd1ebb1SKevin Townsend }
298*4fd1ebb1SKevin Townsend 
299*4fd1ebb1SKevin Townsend /*
300*4fd1ebb1SKevin Townsend  * Low-level master-to-slave transaction handler.
301*4fd1ebb1SKevin Townsend  */
302*4fd1ebb1SKevin Townsend static int lsm303dlhc_mag_send(I2CSlave *i2c, uint8_t data)
303*4fd1ebb1SKevin Townsend {
304*4fd1ebb1SKevin Townsend     LSM303DLHCMagState *s = LSM303DLHC_MAG(i2c);
305*4fd1ebb1SKevin Townsend 
306*4fd1ebb1SKevin Townsend     if (s->len == 0) {
307*4fd1ebb1SKevin Townsend         /* First byte is the reg pointer */
308*4fd1ebb1SKevin Townsend         s->pointer = data;
309*4fd1ebb1SKevin Townsend         s->len++;
310*4fd1ebb1SKevin Townsend     } else if (s->len == 1) {
311*4fd1ebb1SKevin Townsend         /* Second byte is the new register value. */
312*4fd1ebb1SKevin Townsend         s->buf = data;
313*4fd1ebb1SKevin Townsend         lsm303dlhc_mag_write(s);
314*4fd1ebb1SKevin Townsend     } else {
315*4fd1ebb1SKevin Townsend         g_assert_not_reached();
316*4fd1ebb1SKevin Townsend     }
317*4fd1ebb1SKevin Townsend 
318*4fd1ebb1SKevin Townsend     return 0;
319*4fd1ebb1SKevin Townsend }
320*4fd1ebb1SKevin Townsend 
321*4fd1ebb1SKevin Townsend /*
322*4fd1ebb1SKevin Townsend  * Low-level slave-to-master transaction handler (read attempts).
323*4fd1ebb1SKevin Townsend  */
324*4fd1ebb1SKevin Townsend static uint8_t lsm303dlhc_mag_recv(I2CSlave *i2c)
325*4fd1ebb1SKevin Townsend {
326*4fd1ebb1SKevin Townsend     LSM303DLHCMagState *s = LSM303DLHC_MAG(i2c);
327*4fd1ebb1SKevin Townsend     uint8_t resp;
328*4fd1ebb1SKevin Townsend 
329*4fd1ebb1SKevin Townsend     switch (s->pointer) {
330*4fd1ebb1SKevin Townsend     case LSM303DLHC_MAG_REG_CRA:
331*4fd1ebb1SKevin Townsend         resp = s->cra;
332*4fd1ebb1SKevin Townsend         break;
333*4fd1ebb1SKevin Townsend     case LSM303DLHC_MAG_REG_CRB:
334*4fd1ebb1SKevin Townsend         resp = s->crb;
335*4fd1ebb1SKevin Townsend         break;
336*4fd1ebb1SKevin Townsend     case LSM303DLHC_MAG_REG_MR:
337*4fd1ebb1SKevin Townsend         resp = s->mr;
338*4fd1ebb1SKevin Townsend         break;
339*4fd1ebb1SKevin Townsend     case LSM303DLHC_MAG_REG_OUT_X_H:
340*4fd1ebb1SKevin Townsend         resp = (uint8_t)(s->x_lock >> 8);
341*4fd1ebb1SKevin Townsend         break;
342*4fd1ebb1SKevin Townsend     case LSM303DLHC_MAG_REG_OUT_X_L:
343*4fd1ebb1SKevin Townsend         resp = (uint8_t)(s->x_lock);
344*4fd1ebb1SKevin Townsend         break;
345*4fd1ebb1SKevin Townsend     case LSM303DLHC_MAG_REG_OUT_Z_H:
346*4fd1ebb1SKevin Townsend         resp = (uint8_t)(s->z_lock >> 8);
347*4fd1ebb1SKevin Townsend         break;
348*4fd1ebb1SKevin Townsend     case LSM303DLHC_MAG_REG_OUT_Z_L:
349*4fd1ebb1SKevin Townsend         resp = (uint8_t)(s->z_lock);
350*4fd1ebb1SKevin Townsend         break;
351*4fd1ebb1SKevin Townsend     case LSM303DLHC_MAG_REG_OUT_Y_H:
352*4fd1ebb1SKevin Townsend         resp = (uint8_t)(s->y_lock >> 8);
353*4fd1ebb1SKevin Townsend         break;
354*4fd1ebb1SKevin Townsend     case LSM303DLHC_MAG_REG_OUT_Y_L:
355*4fd1ebb1SKevin Townsend         resp = (uint8_t)(s->y_lock);
356*4fd1ebb1SKevin Townsend         break;
357*4fd1ebb1SKevin Townsend     case LSM303DLHC_MAG_REG_SR:
358*4fd1ebb1SKevin Townsend         resp = s->sr;
359*4fd1ebb1SKevin Townsend         break;
360*4fd1ebb1SKevin Townsend     case LSM303DLHC_MAG_REG_IRA:
361*4fd1ebb1SKevin Townsend         resp = s->ira;
362*4fd1ebb1SKevin Townsend         break;
363*4fd1ebb1SKevin Townsend     case LSM303DLHC_MAG_REG_IRB:
364*4fd1ebb1SKevin Townsend         resp = s->irb;
365*4fd1ebb1SKevin Townsend         break;
366*4fd1ebb1SKevin Townsend     case LSM303DLHC_MAG_REG_IRC:
367*4fd1ebb1SKevin Townsend         resp = s->irc;
368*4fd1ebb1SKevin Townsend         break;
369*4fd1ebb1SKevin Townsend     case LSM303DLHC_MAG_REG_TEMP_OUT_H:
370*4fd1ebb1SKevin Townsend         /* Check if the temperature sensor is enabled or not (CRA & 0x80). */
371*4fd1ebb1SKevin Townsend         if (s->cra & 0x80) {
372*4fd1ebb1SKevin Townsend             resp = (uint8_t)(s->temperature_lock >> 8);
373*4fd1ebb1SKevin Townsend         } else {
374*4fd1ebb1SKevin Townsend             resp = 0;
375*4fd1ebb1SKevin Townsend         }
376*4fd1ebb1SKevin Townsend         break;
377*4fd1ebb1SKevin Townsend     case LSM303DLHC_MAG_REG_TEMP_OUT_L:
378*4fd1ebb1SKevin Townsend         if (s->cra & 0x80) {
379*4fd1ebb1SKevin Townsend             resp = (uint8_t)(s->temperature_lock & 0xff);
380*4fd1ebb1SKevin Townsend         } else {
381*4fd1ebb1SKevin Townsend             resp = 0;
382*4fd1ebb1SKevin Townsend         }
383*4fd1ebb1SKevin Townsend         break;
384*4fd1ebb1SKevin Townsend     default:
385*4fd1ebb1SKevin Townsend         resp = 0;
386*4fd1ebb1SKevin Townsend         break;
387*4fd1ebb1SKevin Townsend     }
388*4fd1ebb1SKevin Townsend 
389*4fd1ebb1SKevin Townsend     /*
390*4fd1ebb1SKevin Townsend      * The address pointer on the LSM303DLHC auto-increments whenever a byte
391*4fd1ebb1SKevin Townsend      * is read, without the master device having to request the next address.
392*4fd1ebb1SKevin Townsend      *
393*4fd1ebb1SKevin Townsend      * The auto-increment process has the following logic:
394*4fd1ebb1SKevin Townsend      *
395*4fd1ebb1SKevin Townsend      *   - if (s->pointer == 8) then s->pointer = 3
396*4fd1ebb1SKevin Townsend      *   - else: if (s->pointer == 12) then s->pointer = 0
397*4fd1ebb1SKevin Townsend      *   - else: s->pointer += 1
398*4fd1ebb1SKevin Townsend      *
399*4fd1ebb1SKevin Townsend      * Reading an invalid address return 0.
400*4fd1ebb1SKevin Townsend      */
401*4fd1ebb1SKevin Townsend     if (s->pointer == LSM303DLHC_MAG_REG_OUT_Y_L) {
402*4fd1ebb1SKevin Townsend         s->pointer = LSM303DLHC_MAG_REG_OUT_X_H;
403*4fd1ebb1SKevin Townsend     } else if (s->pointer == LSM303DLHC_MAG_REG_IRC) {
404*4fd1ebb1SKevin Townsend         s->pointer = LSM303DLHC_MAG_REG_CRA;
405*4fd1ebb1SKevin Townsend     } else {
406*4fd1ebb1SKevin Townsend         s->pointer++;
407*4fd1ebb1SKevin Townsend     }
408*4fd1ebb1SKevin Townsend 
409*4fd1ebb1SKevin Townsend     return resp;
410*4fd1ebb1SKevin Townsend }
411*4fd1ebb1SKevin Townsend 
412*4fd1ebb1SKevin Townsend /*
413*4fd1ebb1SKevin Townsend  * Bus state change handler.
414*4fd1ebb1SKevin Townsend  */
415*4fd1ebb1SKevin Townsend static int lsm303dlhc_mag_event(I2CSlave *i2c, enum i2c_event event)
416*4fd1ebb1SKevin Townsend {
417*4fd1ebb1SKevin Townsend     LSM303DLHCMagState *s = LSM303DLHC_MAG(i2c);
418*4fd1ebb1SKevin Townsend 
419*4fd1ebb1SKevin Townsend     switch (event) {
420*4fd1ebb1SKevin Townsend     case I2C_START_SEND:
421*4fd1ebb1SKevin Townsend         break;
422*4fd1ebb1SKevin Townsend     case I2C_START_RECV:
423*4fd1ebb1SKevin Townsend         lsm303dlhc_mag_read(s);
424*4fd1ebb1SKevin Townsend         break;
425*4fd1ebb1SKevin Townsend     case I2C_FINISH:
426*4fd1ebb1SKevin Townsend         lsm303dlhc_mag_finish(s);
427*4fd1ebb1SKevin Townsend         break;
428*4fd1ebb1SKevin Townsend     case I2C_NACK:
429*4fd1ebb1SKevin Townsend         break;
430*4fd1ebb1SKevin Townsend     }
431*4fd1ebb1SKevin Townsend 
432*4fd1ebb1SKevin Townsend     s->len = 0;
433*4fd1ebb1SKevin Townsend     return 0;
434*4fd1ebb1SKevin Townsend }
435*4fd1ebb1SKevin Townsend 
436*4fd1ebb1SKevin Townsend /*
437*4fd1ebb1SKevin Townsend  * Device data description using VMSTATE macros.
438*4fd1ebb1SKevin Townsend  */
439*4fd1ebb1SKevin Townsend static const VMStateDescription vmstate_lsm303dlhc_mag = {
440*4fd1ebb1SKevin Townsend     .name = "LSM303DLHC_MAG",
441*4fd1ebb1SKevin Townsend     .version_id = 0,
442*4fd1ebb1SKevin Townsend     .minimum_version_id = 0,
443*4fd1ebb1SKevin Townsend     .fields = (VMStateField[]) {
444*4fd1ebb1SKevin Townsend 
445*4fd1ebb1SKevin Townsend         VMSTATE_I2C_SLAVE(parent_obj, LSM303DLHCMagState),
446*4fd1ebb1SKevin Townsend         VMSTATE_UINT8(len, LSM303DLHCMagState),
447*4fd1ebb1SKevin Townsend         VMSTATE_UINT8(buf, LSM303DLHCMagState),
448*4fd1ebb1SKevin Townsend         VMSTATE_UINT8(pointer, LSM303DLHCMagState),
449*4fd1ebb1SKevin Townsend         VMSTATE_UINT8(cra, LSM303DLHCMagState),
450*4fd1ebb1SKevin Townsend         VMSTATE_UINT8(crb, LSM303DLHCMagState),
451*4fd1ebb1SKevin Townsend         VMSTATE_UINT8(mr, LSM303DLHCMagState),
452*4fd1ebb1SKevin Townsend         VMSTATE_INT16(x, LSM303DLHCMagState),
453*4fd1ebb1SKevin Townsend         VMSTATE_INT16(z, LSM303DLHCMagState),
454*4fd1ebb1SKevin Townsend         VMSTATE_INT16(y, LSM303DLHCMagState),
455*4fd1ebb1SKevin Townsend         VMSTATE_INT16(x_lock, LSM303DLHCMagState),
456*4fd1ebb1SKevin Townsend         VMSTATE_INT16(z_lock, LSM303DLHCMagState),
457*4fd1ebb1SKevin Townsend         VMSTATE_INT16(y_lock, LSM303DLHCMagState),
458*4fd1ebb1SKevin Townsend         VMSTATE_UINT8(sr, LSM303DLHCMagState),
459*4fd1ebb1SKevin Townsend         VMSTATE_UINT8(ira, LSM303DLHCMagState),
460*4fd1ebb1SKevin Townsend         VMSTATE_UINT8(irb, LSM303DLHCMagState),
461*4fd1ebb1SKevin Townsend         VMSTATE_UINT8(irc, LSM303DLHCMagState),
462*4fd1ebb1SKevin Townsend         VMSTATE_INT16(temperature, LSM303DLHCMagState),
463*4fd1ebb1SKevin Townsend         VMSTATE_INT16(temperature_lock, LSM303DLHCMagState),
464*4fd1ebb1SKevin Townsend         VMSTATE_END_OF_LIST()
465*4fd1ebb1SKevin Townsend     }
466*4fd1ebb1SKevin Townsend };
467*4fd1ebb1SKevin Townsend 
468*4fd1ebb1SKevin Townsend /*
469*4fd1ebb1SKevin Townsend  * Put the device into post-reset default state.
470*4fd1ebb1SKevin Townsend  */
471*4fd1ebb1SKevin Townsend static void lsm303dlhc_mag_default_cfg(LSM303DLHCMagState *s)
472*4fd1ebb1SKevin Townsend {
473*4fd1ebb1SKevin Townsend     /* Set the device into is default reset state. */
474*4fd1ebb1SKevin Townsend     s->len = 0;
475*4fd1ebb1SKevin Townsend     s->pointer = 0;         /* Current register. */
476*4fd1ebb1SKevin Townsend     s->buf = 0;             /* Shared buffer. */
477*4fd1ebb1SKevin Townsend     s->cra = 0x10;          /* Temp Enabled = 0, Data Rate = 15.0 Hz. */
478*4fd1ebb1SKevin Townsend     s->crb = 0x20;          /* Gain = +/- 1.3 Gauss. */
479*4fd1ebb1SKevin Townsend     s->mr = 0x3;            /* Operating Mode = Sleep. */
480*4fd1ebb1SKevin Townsend     s->x = 0;
481*4fd1ebb1SKevin Townsend     s->z = 0;
482*4fd1ebb1SKevin Townsend     s->y = 0;
483*4fd1ebb1SKevin Townsend     s->x_lock = 0;
484*4fd1ebb1SKevin Townsend     s->z_lock = 0;
485*4fd1ebb1SKevin Townsend     s->y_lock = 0;
486*4fd1ebb1SKevin Townsend     s->sr = 0x1;            /* DRDY = 1. */
487*4fd1ebb1SKevin Townsend     s->ira = 0x48;
488*4fd1ebb1SKevin Townsend     s->irb = 0x34;
489*4fd1ebb1SKevin Townsend     s->irc = 0x33;
490*4fd1ebb1SKevin Townsend     s->temperature = 0;     /* Default to 0 degrees C (0/8 lsb = 0 C). */
491*4fd1ebb1SKevin Townsend     s->temperature_lock = 0;
492*4fd1ebb1SKevin Townsend }
493*4fd1ebb1SKevin Townsend 
494*4fd1ebb1SKevin Townsend /*
495*4fd1ebb1SKevin Townsend  * Callback handler when DeviceState 'reset' is set to true.
496*4fd1ebb1SKevin Townsend  */
497*4fd1ebb1SKevin Townsend static void lsm303dlhc_mag_reset(DeviceState *dev)
498*4fd1ebb1SKevin Townsend {
499*4fd1ebb1SKevin Townsend     I2CSlave *i2c = I2C_SLAVE(dev);
500*4fd1ebb1SKevin Townsend     LSM303DLHCMagState *s = LSM303DLHC_MAG(i2c);
501*4fd1ebb1SKevin Townsend 
502*4fd1ebb1SKevin Townsend     /* Set the device into its default reset state. */
503*4fd1ebb1SKevin Townsend     lsm303dlhc_mag_default_cfg(s);
504*4fd1ebb1SKevin Townsend }
505*4fd1ebb1SKevin Townsend 
506*4fd1ebb1SKevin Townsend /*
507*4fd1ebb1SKevin Townsend  * Initialisation of any public properties.
508*4fd1ebb1SKevin Townsend  */
509*4fd1ebb1SKevin Townsend static void lsm303dlhc_mag_initfn(Object *obj)
510*4fd1ebb1SKevin Townsend {
511*4fd1ebb1SKevin Townsend     object_property_add(obj, "mag-x", "int",
512*4fd1ebb1SKevin Townsend                 lsm303dlhc_mag_get_x,
513*4fd1ebb1SKevin Townsend                 lsm303dlhc_mag_set_x, NULL, NULL);
514*4fd1ebb1SKevin Townsend 
515*4fd1ebb1SKevin Townsend     object_property_add(obj, "mag-y", "int",
516*4fd1ebb1SKevin Townsend                 lsm303dlhc_mag_get_y,
517*4fd1ebb1SKevin Townsend                 lsm303dlhc_mag_set_y, NULL, NULL);
518*4fd1ebb1SKevin Townsend 
519*4fd1ebb1SKevin Townsend     object_property_add(obj, "mag-z", "int",
520*4fd1ebb1SKevin Townsend                 lsm303dlhc_mag_get_z,
521*4fd1ebb1SKevin Townsend                 lsm303dlhc_mag_set_z, NULL, NULL);
522*4fd1ebb1SKevin Townsend 
523*4fd1ebb1SKevin Townsend     object_property_add(obj, "temperature", "int",
524*4fd1ebb1SKevin Townsend                 lsm303dlhc_mag_get_temperature,
525*4fd1ebb1SKevin Townsend                 lsm303dlhc_mag_set_temperature, NULL, NULL);
526*4fd1ebb1SKevin Townsend }
527*4fd1ebb1SKevin Townsend 
528*4fd1ebb1SKevin Townsend /*
529*4fd1ebb1SKevin Townsend  * Set the virtual method pointers (bus state change, tx/rx, etc.).
530*4fd1ebb1SKevin Townsend  */
531*4fd1ebb1SKevin Townsend static void lsm303dlhc_mag_class_init(ObjectClass *klass, void *data)
532*4fd1ebb1SKevin Townsend {
533*4fd1ebb1SKevin Townsend     DeviceClass *dc = DEVICE_CLASS(klass);
534*4fd1ebb1SKevin Townsend     I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
535*4fd1ebb1SKevin Townsend 
536*4fd1ebb1SKevin Townsend     dc->reset = lsm303dlhc_mag_reset;
537*4fd1ebb1SKevin Townsend     dc->vmsd = &vmstate_lsm303dlhc_mag;
538*4fd1ebb1SKevin Townsend     k->event = lsm303dlhc_mag_event;
539*4fd1ebb1SKevin Townsend     k->recv = lsm303dlhc_mag_recv;
540*4fd1ebb1SKevin Townsend     k->send = lsm303dlhc_mag_send;
541*4fd1ebb1SKevin Townsend }
542*4fd1ebb1SKevin Townsend 
543*4fd1ebb1SKevin Townsend static const TypeInfo lsm303dlhc_mag_info = {
544*4fd1ebb1SKevin Townsend     .name = TYPE_LSM303DLHC_MAG,
545*4fd1ebb1SKevin Townsend     .parent = TYPE_I2C_SLAVE,
546*4fd1ebb1SKevin Townsend     .instance_size = sizeof(LSM303DLHCMagState),
547*4fd1ebb1SKevin Townsend     .instance_init = lsm303dlhc_mag_initfn,
548*4fd1ebb1SKevin Townsend     .class_init = lsm303dlhc_mag_class_init,
549*4fd1ebb1SKevin Townsend };
550*4fd1ebb1SKevin Townsend 
551*4fd1ebb1SKevin Townsend static void lsm303dlhc_mag_register_types(void)
552*4fd1ebb1SKevin Townsend {
553*4fd1ebb1SKevin Townsend     type_register_static(&lsm303dlhc_mag_info);
554*4fd1ebb1SKevin Townsend }
555*4fd1ebb1SKevin Townsend 
556*4fd1ebb1SKevin Townsend type_init(lsm303dlhc_mag_register_types)
557