1 /*
2 * Texas Instruments TMP105 temperature sensor.
3 *
4 * Copyright (C) 2008 Nokia Corporation
5 * Written by Andrzej Zaborowski <andrew@openedhand.com>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 or
10 * (at your option) version 3 of the License.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "qemu/osdep.h"
22 #include "hw/i2c/i2c.h"
23 #include "hw/irq.h"
24 #include "migration/vmstate.h"
25 #include "hw/sensor/tmp105.h"
26 #include "qapi/error.h"
27 #include "qapi/visitor.h"
28 #include "qemu/module.h"
29 #include "hw/registerfields.h"
30 #include "trace.h"
31
32 FIELD(CONFIG, SHUTDOWN_MODE, 0, 1)
33 FIELD(CONFIG, THERMOSTAT_MODE, 1, 1)
34 FIELD(CONFIG, POLARITY, 2, 1)
35 FIELD(CONFIG, FAULT_QUEUE, 3, 2)
36 FIELD(CONFIG, CONVERTER_RESOLUTION, 5, 2)
37 FIELD(CONFIG, ONE_SHOT, 7, 1)
38
tmp105_interrupt_update(TMP105State * s)39 static void tmp105_interrupt_update(TMP105State *s)
40 {
41 qemu_set_irq(s->pin, s->alarm ^ FIELD_EX8(~s->config, CONFIG, POLARITY));
42 }
43
tmp105_alarm_update(TMP105State * s,bool one_shot)44 static void tmp105_alarm_update(TMP105State *s, bool one_shot)
45 {
46 if (FIELD_EX8(s->config, CONFIG, SHUTDOWN_MODE) && !one_shot) {
47 return;
48 }
49
50 if (FIELD_EX8(s->config, CONFIG, THERMOSTAT_MODE)) {
51 /*
52 * TM == 1 : Interrupt mode. We signal Alert when the
53 * temperature rises above T_high, and expect the guest to clear
54 * it (eg by reading a device register).
55 */
56 if (s->detect_falling) {
57 if (s->temperature < s->limit[0]) {
58 s->alarm = 1;
59 s->detect_falling = false;
60 }
61 } else {
62 if (s->temperature >= s->limit[1]) {
63 s->alarm = 1;
64 s->detect_falling = true;
65 }
66 }
67 } else {
68 /*
69 * TM == 0 : Comparator mode. We signal Alert when the temperature
70 * rises above T_high, and stop signalling it when the temperature
71 * falls below T_low.
72 */
73 if (s->detect_falling) {
74 if (s->temperature < s->limit[0]) {
75 s->alarm = 0;
76 s->detect_falling = false;
77 }
78 } else {
79 if (s->temperature >= s->limit[1]) {
80 s->alarm = 1;
81 s->detect_falling = true;
82 }
83 }
84 }
85
86 tmp105_interrupt_update(s);
87 }
88
tmp105_get_temperature(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)89 static void tmp105_get_temperature(Object *obj, Visitor *v, const char *name,
90 void *opaque, Error **errp)
91 {
92 TMP105State *s = TMP105(obj);
93 int64_t value = s->temperature * 1000 / 256;
94
95 visit_type_int(v, name, &value, errp);
96 }
97
98 /*
99 * Units are 0.001 centigrades relative to 0 C. s->temperature is 8.8
100 * fixed point, so units are 1/256 centigrades. A simple ratio will do.
101 */
tmp105_set_temperature(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)102 static void tmp105_set_temperature(Object *obj, Visitor *v, const char *name,
103 void *opaque, Error **errp)
104 {
105 TMP105State *s = TMP105(obj);
106 int64_t temp;
107
108 if (!visit_type_int(v, name, &temp, errp)) {
109 return;
110 }
111 if (temp >= 128000 || temp < -128000) {
112 error_setg(errp, "value %" PRId64 ".%03" PRIu64 " C is out of range",
113 temp / 1000, temp % 1000);
114 return;
115 }
116
117 s->temperature = (int16_t) (temp * 256 / 1000);
118
119 tmp105_alarm_update(s, false);
120 }
121
122 static const int tmp105_faultq[4] = { 1, 2, 4, 6 };
123
tmp105_read(TMP105State * s)124 static void tmp105_read(TMP105State *s)
125 {
126 s->len = 0;
127
128 if (FIELD_EX8(s->config, CONFIG, THERMOSTAT_MODE)) {
129 s->alarm = 0;
130 tmp105_interrupt_update(s);
131 }
132
133 switch (s->pointer & 3) {
134 case TMP105_REG_TEMPERATURE:
135 s->buf[s->len++] = (((uint16_t) s->temperature) >> 8);
136 s->buf[s->len++] = (((uint16_t) s->temperature) >> 0) &
137 (0xf0 << (FIELD_EX8(~s->config, CONFIG, CONVERTER_RESOLUTION)));
138 break;
139
140 case TMP105_REG_CONFIG:
141 s->buf[s->len++] = s->config;
142 break;
143
144 case TMP105_REG_T_LOW:
145 s->buf[s->len++] = ((uint16_t) s->limit[0]) >> 8;
146 s->buf[s->len++] = ((uint16_t) s->limit[0]) >> 0;
147 break;
148
149 case TMP105_REG_T_HIGH:
150 s->buf[s->len++] = ((uint16_t) s->limit[1]) >> 8;
151 s->buf[s->len++] = ((uint16_t) s->limit[1]) >> 0;
152 break;
153 }
154
155 trace_tmp105_read(s->i2c.address, s->pointer);
156 }
157
tmp105_write(TMP105State * s)158 static void tmp105_write(TMP105State *s)
159 {
160 trace_tmp105_write(s->i2c.address, s->pointer);
161
162 switch (s->pointer & 3) {
163 case TMP105_REG_TEMPERATURE:
164 break;
165
166 case TMP105_REG_CONFIG:
167 if (FIELD_EX8(s->buf[0] & ~s->config, CONFIG, SHUTDOWN_MODE)) {
168 trace_tmp105_write_shutdown(s->i2c.address);
169 }
170 s->config = FIELD_DP8(s->buf[0], CONFIG, ONE_SHOT, 0);
171 s->faults = tmp105_faultq[FIELD_EX8(s->config, CONFIG, FAULT_QUEUE)];
172 tmp105_alarm_update(s, FIELD_EX8(s->buf[0], CONFIG, ONE_SHOT));
173 break;
174
175 case TMP105_REG_T_LOW:
176 case TMP105_REG_T_HIGH:
177 if (s->len >= 3) {
178 s->limit[s->pointer & 1] = (int16_t)
179 ((((uint16_t) s->buf[0]) << 8) | (s->buf[1] & 0xf0));
180 }
181 tmp105_alarm_update(s, false);
182 break;
183 }
184 }
185
tmp105_rx(I2CSlave * i2c)186 static uint8_t tmp105_rx(I2CSlave *i2c)
187 {
188 TMP105State *s = TMP105(i2c);
189
190 if (s->len < 2) {
191 return s->buf[s->len++];
192 } else {
193 return 0xff;
194 }
195 }
196
tmp105_tx(I2CSlave * i2c,uint8_t data)197 static int tmp105_tx(I2CSlave *i2c, uint8_t data)
198 {
199 TMP105State *s = TMP105(i2c);
200
201 if (s->len == 0) {
202 s->pointer = data;
203 s->len++;
204 } else {
205 if (s->len <= 2) {
206 s->buf[s->len - 1] = data;
207 }
208 s->len++;
209 tmp105_write(s);
210 }
211
212 return 0;
213 }
214
tmp105_event(I2CSlave * i2c,enum i2c_event event)215 static int tmp105_event(I2CSlave *i2c, enum i2c_event event)
216 {
217 TMP105State *s = TMP105(i2c);
218
219 if (event == I2C_START_RECV) {
220 tmp105_read(s);
221 }
222
223 s->len = 0;
224 return 0;
225 }
226
tmp105_post_load(void * opaque,int version_id)227 static int tmp105_post_load(void *opaque, int version_id)
228 {
229 TMP105State *s = opaque;
230
231 s->faults = tmp105_faultq[FIELD_EX8(s->config, CONFIG, FAULT_QUEUE)];
232
233 tmp105_interrupt_update(s);
234 return 0;
235 }
236
detect_falling_needed(void * opaque)237 static bool detect_falling_needed(void *opaque)
238 {
239 TMP105State *s = opaque;
240
241 /*
242 * We only need to migrate the detect_falling bool if it's set;
243 * for migration from older machines we assume that it is false
244 * (ie temperature is not out of range).
245 */
246 return s->detect_falling;
247 }
248
249 static const VMStateDescription vmstate_tmp105_detect_falling = {
250 .name = "TMP105/detect-falling",
251 .version_id = 1,
252 .minimum_version_id = 1,
253 .needed = detect_falling_needed,
254 .fields = (const VMStateField[]) {
255 VMSTATE_BOOL(detect_falling, TMP105State),
256 VMSTATE_END_OF_LIST()
257 }
258 };
259
260 static const VMStateDescription vmstate_tmp105 = {
261 .name = "TMP105",
262 .version_id = 0,
263 .minimum_version_id = 0,
264 .post_load = tmp105_post_load,
265 .fields = (const VMStateField[]) {
266 VMSTATE_UINT8(len, TMP105State),
267 VMSTATE_UINT8_ARRAY(buf, TMP105State, 2),
268 VMSTATE_UINT8(pointer, TMP105State),
269 VMSTATE_UINT8(config, TMP105State),
270 VMSTATE_INT16(temperature, TMP105State),
271 VMSTATE_INT16_ARRAY(limit, TMP105State, 2),
272 VMSTATE_UINT8(alarm, TMP105State),
273 VMSTATE_I2C_SLAVE(i2c, TMP105State),
274 VMSTATE_END_OF_LIST()
275 },
276 .subsections = (const VMStateDescription * const []) {
277 &vmstate_tmp105_detect_falling,
278 NULL
279 }
280 };
281
tmp105_reset(I2CSlave * i2c)282 static void tmp105_reset(I2CSlave *i2c)
283 {
284 TMP105State *s = TMP105(i2c);
285
286 s->temperature = 0;
287 s->pointer = 0;
288 s->config = 0;
289 s->faults = tmp105_faultq[FIELD_EX8(s->config, CONFIG, FAULT_QUEUE)];
290 s->alarm = 0;
291 s->detect_falling = false;
292
293 s->limit[0] = 0x4b00; /* T_LOW, 75 degrees C */
294 s->limit[1] = 0x5000; /* T_HIGH, 80 degrees C */
295
296 tmp105_interrupt_update(s);
297 }
298
tmp105_realize(DeviceState * dev,Error ** errp)299 static void tmp105_realize(DeviceState *dev, Error **errp)
300 {
301 I2CSlave *i2c = I2C_SLAVE(dev);
302 TMP105State *s = TMP105(i2c);
303
304 qdev_init_gpio_out(&i2c->qdev, &s->pin, 1);
305
306 tmp105_reset(&s->i2c);
307 }
308
tmp105_initfn(Object * obj)309 static void tmp105_initfn(Object *obj)
310 {
311 object_property_add(obj, "temperature", "int",
312 tmp105_get_temperature,
313 tmp105_set_temperature, NULL, NULL);
314 }
315
tmp105_class_init(ObjectClass * klass,void * data)316 static void tmp105_class_init(ObjectClass *klass, void *data)
317 {
318 DeviceClass *dc = DEVICE_CLASS(klass);
319 I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
320
321 dc->realize = tmp105_realize;
322 k->event = tmp105_event;
323 k->recv = tmp105_rx;
324 k->send = tmp105_tx;
325 dc->vmsd = &vmstate_tmp105;
326 }
327
328 static const TypeInfo tmp105_info = {
329 .name = TYPE_TMP105,
330 .parent = TYPE_I2C_SLAVE,
331 .instance_size = sizeof(TMP105State),
332 .instance_init = tmp105_initfn,
333 .class_init = tmp105_class_init,
334 };
335
tmp105_register_types(void)336 static void tmp105_register_types(void)
337 {
338 type_register_static(&tmp105_info);
339 }
340
341 type_init(tmp105_register_types)
342