Patchwork [v1,5/6] hw: pca9548: Device model

login
register
mail settings
Submitter Peter Crosthwaite
Date Feb. 20, 2013, 5:29 a.m.
Message ID <6e0f5e1d3f50c3f5256179b0e5d9cc785b099a40.1361337686.git.peter.crosthwaite@xilinx.com>
Download mbox | patch
Permalink /patch/221952/
State New
Headers show

Comments

Peter Crosthwaite - Feb. 20, 2013, 5:29 a.m.
Initial version of device model for PCA9548 8 way I2C switch.

Signed-off-by: Peter Crosthwaite <peter.crosthwaite@xilinx.com>
---

 default-configs/arm-softmmu.mak |    1 +
 hw/Makefile.objs                |    1 +
 hw/pca9548.c                    |  229 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 231 insertions(+), 0 deletions(-)
 create mode 100644 hw/pca9548.c

Patch

diff --git a/default-configs/arm-softmmu.mak b/default-configs/arm-softmmu.mak
index 9114382..27f2a19 100644
--- a/default-configs/arm-softmmu.mak
+++ b/default-configs/arm-softmmu.mak
@@ -27,6 +27,7 @@  CONFIG_LAN9118=y
 CONFIG_SMC91C111=y
 CONFIG_DS1338=y
 CONFIG_M24CXX=y
+CONFIG_PCA9548=y
 CONFIG_PFLASH_CFI01=y
 CONFIG_PFLASH_CFI02=y
 
diff --git a/hw/Makefile.objs b/hw/Makefile.objs
index dc75c9f..56f1c00 100644
--- a/hw/Makefile.objs
+++ b/hw/Makefile.objs
@@ -179,6 +179,7 @@  common-obj-$(CONFIG_MAX111X) += max111x.o
 common-obj-$(CONFIG_DS1338) += ds1338.o
 common-obj-y += i2c.o smbus.o smbus_eeprom.o
 common-obj-$(CONFIG_M24CXX) += m24cxx.o
+common-obj-$(CONFIG_PCA9548) += pca9548.o
 common-obj-y += eeprom93xx.o
 common-obj-y += scsi-disk.o cdrom.o hd-geometry.o block-common.o
 common-obj-y += scsi-generic.o scsi-bus.o
diff --git a/hw/pca9548.c b/hw/pca9548.c
new file mode 100644
index 0000000..c1d57ae
--- /dev/null
+++ b/hw/pca9548.c
@@ -0,0 +1,229 @@ 
+/*
+ * PCA9548 I2C Switch Dummy model
+ *
+ * Copyright (c) 2012 Xilinx Inc.
+ * Copyright (c) 2012 Peter Crosthwaite <peter.crosthwaite@xilinx.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 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "i2c.h"
+#include "hw.h"
+#include "sysemu/blockdev.h"
+
+#ifndef PCA9548_DEBUG
+#define PCA9548_DEBUG 0
+#endif
+#define DB_PRINT(fmt, args...) do {\
+    if (PCA9548_DEBUG) {\
+        fprintf(stderr, "PCA9548: "fmt, ## args);\
+    } \
+} while (0);
+
+#define NUM_BUSSES 8
+#define pca9548_CONTROL_ADDR 0x74
+
+typedef struct {
+    I2CSlave i2c;
+    i2c_bus *busses[NUM_BUSSES];
+
+    /*state */
+    uint8_t control_reg;
+    enum i2c_event event;
+    bool control_decoded;
+
+    uint8_t chip_enable; /*property */
+} PCA9548State;
+
+#define TYPE_PCA9548 "pca-9548"
+
+#define PCA9548(obj) \
+     OBJECT_CHECK(PCA9548State, (obj), TYPE_PCA9548)
+
+static void pca9548_reset(DeviceState *dev)
+{
+    PCA9548State *s = PCA9548(dev);
+
+    s->control_reg = 0;
+}
+
+static int pca9548_recv(I2CSlave *i2c)
+{
+    PCA9548State *s = PCA9548(i2c);
+    int i;
+    int ret = 0;
+
+    if (s->control_decoded) {
+        ret |= s->control_reg;
+        DB_PRINT("returning control register: %x\n", ret);
+    } else {
+        for (i = 0; i < NUM_BUSSES; ++i) {
+            if (s->control_reg & (1 << i)) {
+                ret |= i2c_recv(s->busses[i]);
+                DB_PRINT("recieving from active bus %d:%x\n", i, ret);
+            }
+        }
+    }
+
+    return ret;
+}
+
+static int pca9548_send(I2CSlave *i2c, uint8_t data)
+{
+    PCA9548State *s = PCA9548(i2c);
+    int i;
+    int ret = -1;
+
+    if (s->control_decoded) {
+        DB_PRINT("setting control register: %x\n", data);
+        s->control_reg = data;
+        ret = 0;
+    } else {
+        for (i = 0; i < NUM_BUSSES; ++i) {
+            if (s->control_reg & (1 << i)) {
+                DB_PRINT("sending to active bus %d:%x\n", i, data);
+                ret &= i2c_send(s->busses[i], data);
+            }
+        }
+    }
+
+    return ret;
+}
+
+static void pca9548_event(I2CSlave *i2c, enum i2c_event event)
+{
+    PCA9548State *s = PCA9548(i2c);
+    int i;
+
+    s->event = event;
+    for (i = 0; i < NUM_BUSSES; ++i) {
+        if (s->control_reg & (1 << i)) {
+            switch (event) {
+            /* defer START conditions until we have an address */
+            case I2C_START_SEND:
+            case I2C_START_RECV:
+                break;
+            /* Forward others to sub busses */
+            case I2C_FINISH:
+                if (!s->control_decoded) {
+                    DB_PRINT("stopping active bus %d\n", i);
+                    i2c_end_transfer(s->busses[i]);
+                }
+                break;
+            case I2C_NACK:
+                if (!s->control_decoded) {
+                    DB_PRINT("nacking active bus %d\n", i);
+                    i2c_nack(s->busses[i]);
+                }
+                break;
+            }
+        }
+    }
+}
+
+static void pca9548_decode_address(I2CSlave *i2c, uint8_t address)
+{
+    PCA9548State *s = PCA9548(i2c);
+    int i;
+
+    s->control_decoded = address ==
+                    (pca9548_CONTROL_ADDR | (s->chip_enable & 0x7));
+    if (s->control_decoded) {
+        return;
+    }
+    for (i = 0; i < NUM_BUSSES; ++i) {
+        if (s->control_reg & (1 << i)) {
+            DB_PRINT("starting active bus %d addr:%02x rnw:%d\n", i, address,
+                    s->event == I2C_START_RECV);
+            i2c_start_transfer(s->busses[i], address,
+                               s->event == I2C_START_RECV);
+        }
+    }
+}
+
+static void pca9548_init(Object *obj)
+{
+    int i;
+    PCA9548State *s = PCA9548(obj);
+
+    for (i = 0; i < NUM_BUSSES; ++i) {
+        char bus_name[16];
+
+        snprintf(bus_name, sizeof(bus_name), "i2c@%d", i);
+        s->busses[i] = i2c_init_bus(DEVICE(s), bus_name);
+    }
+}
+
+static void pca9548_realize(DeviceState *dev, Error **errp)
+{
+    I2CSlave *i2cs =  I2C_SLAVE(dev);
+
+    /* Switch decodes the enitre address range, trample any previously set
+     * values for address and range
+     */
+    i2cs->address = 0;
+    i2cs->address_range = 0x80;
+}
+
+static const VMStateDescription vmstate_PCA9548 = {
+    .name = "pca9548",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_I2C_SLAVE(i2c, PCA9548State),
+        VMSTATE_UINT8(control_reg, PCA9548State),
+        VMSTATE_BOOL(control_decoded, PCA9548State),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static Property pca9548_properties[] = {
+    /* These could be GPIOs, but the application is rare, just let machine model
+     * tie them with props
+     */
+    DEFINE_PROP_UINT8("chip-enable", PCA9548State, chip_enable, 0),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void pca9548_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
+
+    k->event = pca9548_event;
+    k->recv = pca9548_recv;
+    k->send = pca9548_send;
+    k->decode_address = pca9548_decode_address;
+
+    dc->realize = pca9548_realize;
+    dc->reset = pca9548_reset;
+    dc->vmsd = &vmstate_PCA9548;
+    dc->props = pca9548_properties;
+}
+
+static TypeInfo pca9548_info = {
+    .name          = TYPE_PCA9548,
+    .parent        = TYPE_I2C_SLAVE,
+    .instance_size = sizeof(PCA9548State),
+    .instance_init = pca9548_init,
+    .class_init    = pca9548_class_init,
+};
+
+static void pca9548_register_types(void)
+{
+    type_register_static(&pca9548_info);
+}
+
+type_init(pca9548_register_types)