diff options
Diffstat (limited to 'hw/tmp105.c')
-rw-r--r-- | hw/tmp105.c | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/hw/tmp105.c b/hw/tmp105.c new file mode 100644 index 0000000000..6d0505d2dc --- /dev/null +++ b/hw/tmp105.c @@ -0,0 +1,249 @@ +/* + * Texas Instruments TMP105 temperature sensor. + * + * Copyright (C) 2008 Nokia Corporation + * Written by Andrzej Zaborowski <andrew@openedhand.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include "hw.h" +#include "i2c.h" + +struct tmp105_s { + i2c_slave i2c; + int len; + uint8_t buf[2]; + qemu_irq pin; + + uint8_t pointer; + uint8_t config; + int16_t temperature; + int16_t limit[2]; + int faults; + int alarm; +}; + +static void tmp105_interrupt_update(struct tmp105_s *s) +{ + qemu_set_irq(s->pin, s->alarm ^ ((~s->config >> 2) & 1)); /* POL */ +} + +static void tmp105_alarm_update(struct tmp105_s *s) +{ + if ((s->config >> 0) & 1) { /* SD */ + if ((s->config >> 7) & 1) /* OS */ + s->config &= ~(1 << 7); /* OS */ + else + return; + } + + if ((s->config >> 1) & 1) { /* TM */ + if (s->temperature >= s->limit[1]) + s->alarm = 1; + else if (s->temperature < s->limit[0]) + s->alarm = 1; + } else { + if (s->temperature >= s->limit[1]) + s->alarm = 1; + else if (s->temperature < s->limit[0]) + s->alarm = 0; + } + + tmp105_interrupt_update(s); +} + +/* Units are 0.001 centigrades relative to 0 C. */ +void tmp105_set(i2c_slave *i2c, int temp) +{ + struct tmp105_s *s = (struct tmp105_s *) i2c; + + if (temp >= 128000 || temp < -128000) { + fprintf(stderr, "%s: values is out of range (%i.%03i C)\n", + __FUNCTION__, temp / 1000, temp % 1000); + exit(-1); + } + + s->temperature = ((int16_t) (temp * 0x800 / 128000)) << 4; + + tmp105_alarm_update(s); +} + +static const int tmp105_faultq[4] = { 1, 2, 4, 6 }; + +static void tmp105_read(struct tmp105_s *s) +{ + s->len = 0; + + if ((s->config >> 1) & 1) { /* TM */ + s->alarm = 0; + tmp105_interrupt_update(s); + } + + switch (s->pointer & 3) { + case 0: /* Temperature */ + s->buf[s->len ++] = (((uint16_t) s->temperature) >> 8); + s->buf[s->len ++] = (((uint16_t) s->temperature) >> 0) & + (0xf0 << ((~s->config >> 5) & 3)); /* R */ + break; + + case 1: /* Configuration */ + s->buf[s->len ++] = s->config; + break; + + case 2: /* T_LOW */ + s->buf[s->len ++] = ((uint16_t) s->limit[0]) >> 8; + s->buf[s->len ++] = ((uint16_t) s->limit[0]) >> 0; + break; + + case 3: /* T_HIGH */ + s->buf[s->len ++] = ((uint16_t) s->limit[1]) >> 8; + s->buf[s->len ++] = ((uint16_t) s->limit[1]) >> 0; + break; + } +} + +static void tmp105_write(struct tmp105_s *s) +{ + switch (s->pointer & 3) { + case 0: /* Temperature */ + break; + + case 1: /* Configuration */ + if (s->buf[0] & ~s->config & (1 << 0)) /* SD */ + printf("%s: TMP105 shutdown\n", __FUNCTION__); + s->config = s->buf[0]; + s->faults = tmp105_faultq[(s->config >> 3) & 3]; /* F */ + tmp105_alarm_update(s); + break; + + case 2: /* T_LOW */ + case 3: /* T_HIGH */ + if (s->len >= 3) + s->limit[s->pointer & 1] = (int16_t) + ((((uint16_t) s->buf[0]) << 8) | s->buf[1]); + tmp105_alarm_update(s); + break; + } +} + +static int tmp105_rx(i2c_slave *i2c) +{ + struct tmp105_s *s = (struct tmp105_s *) i2c; + + if (s->len < 2) + return s->buf[s->len ++]; + else + return 0xff; +} + +static int tmp105_tx(i2c_slave *i2c, uint8_t data) +{ + struct tmp105_s *s = (struct tmp105_s *) i2c; + + if (!s->len ++) + s->pointer = data; + else { + if (s->len <= 2) + s->buf[s->len - 1] = data; + tmp105_write(s); + } + + return 0; +} + +static void tmp105_event(i2c_slave *i2c, enum i2c_event event) +{ + struct tmp105_s *s = (struct tmp105_s *) i2c; + + if (event == I2C_START_RECV) + tmp105_read(s); + + s->len = 0; +} + +static void tmp105_save(QEMUFile *f, void *opaque) +{ + struct tmp105_s *s = (struct tmp105_s *) opaque; + + qemu_put_byte(f, s->len); + qemu_put_8s(f, &s->buf[0]); + qemu_put_8s(f, &s->buf[1]); + + qemu_put_8s(f, &s->pointer); + qemu_put_8s(f, &s->config); + qemu_put_be16s(f, &s->temperature); + qemu_put_be16s(f, &s->limit[0]); + qemu_put_be16s(f, &s->limit[1]); + qemu_put_byte(f, s->alarm); + s->faults = tmp105_faultq[(s->config >> 3) & 3]; /* F */ + + i2c_slave_save(f, &s->i2c); +} + +static int tmp105_load(QEMUFile *f, void *opaque, int version_id) +{ + struct tmp105_s *s = (struct tmp105_s *) opaque; + + s->len = qemu_get_byte(f); + qemu_get_8s(f, &s->buf[0]); + qemu_get_8s(f, &s->buf[1]); + + qemu_get_8s(f, &s->pointer); + qemu_get_8s(f, &s->config); + qemu_get_be16s(f, &s->temperature); + qemu_get_be16s(f, &s->limit[0]); + qemu_get_be16s(f, &s->limit[1]); + s->alarm = qemu_get_byte(f); + + tmp105_interrupt_update(s); + + i2c_slave_load(f, &s->i2c); + return 0; +} + +void tmp105_reset(i2c_slave *i2c) +{ + struct tmp105_s *s = (struct tmp105_s *) i2c; + + s->temperature = 0; + s->pointer = 0; + s->config = 0; + s->faults = tmp105_faultq[(s->config >> 3) & 3]; + s->alarm = 0; + + tmp105_interrupt_update(s); +} + +static int tmp105_iid = 0; + +struct i2c_slave *tmp105_init(i2c_bus *bus, qemu_irq alarm) +{ + struct tmp105_s *s = (struct tmp105_s *) + i2c_slave_init(bus, 0, sizeof(struct tmp105_s)); + + s->i2c.event = tmp105_event; + s->i2c.recv = tmp105_rx; + s->i2c.send = tmp105_tx; + s->pin = alarm; + + tmp105_reset(&s->i2c); + + register_savevm("TMP105", tmp105_iid ++, 0, + tmp105_save, tmp105_load, s); + + return &s->i2c; +} |