qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH v3 0/4] Add i.MX25 support through the 3DS evaluation board
@ 2013-05-05 14:28 Jean-Christophe DUBOIS
  2013-05-05 14:28 ` [Qemu-devel] [PATCH v3 1/4] Add i.MX FEC Ethernet emulator Jean-Christophe DUBOIS
                   ` (3 more replies)
  0 siblings, 4 replies; 7+ messages in thread
From: Jean-Christophe DUBOIS @ 2013-05-05 14:28 UTC (permalink / raw)
  To: qemu-devel
  Cc: peter.maydell, peter.crosthwaite, peter.chubb, afaerber,
	Jean-Christophe DUBOIS

This series of patches add the support for the i.MX25 processor through the
Freescale 3DS evaluation board.

For now a limited set of devices are supported.
    * GPT timers (from i.MX31)
    * EPIT timers (from i.MX31)
    * Serial ports (from i.MX31)
    * Ethernet FEC port
    * I2C controller

It also adds qtest support for the I2C controller.

Jean-Christophe DUBOIS (4):
  Add i.MX FEC Ethernet emulator
  Add i.MX I2C controller emulator
  Add i.MX25 3DS evaluation board support.
  Add qtest support for i.MX I2C device emulation.

 default-configs/arm-softmmu.mak |   3 +
 hw/arm/Makefile.objs            |   1 +
 hw/arm/imx25_3ds.c              | 258 +++++++++++++
 hw/i2c/Makefile.objs            |   1 +
 hw/i2c/imx_i2c.c                | 352 ++++++++++++++++++
 hw/i2c/imx_i2c_regs.h           |  63 ++++
 hw/net/Makefile.objs            |   1 +
 hw/net/imx_fec.c                | 793 ++++++++++++++++++++++++++++++++++++++++
 include/hw/arm/imx.h            |   1 +
 tests/Makefile                  |   3 +
 tests/ds1338-test.c             |  64 ++++
 tests/libqos/i2c-imx.c          | 198 ++++++++++
 tests/libqos/i2c.h              |   3 +
 13 files changed, 1741 insertions(+)
 create mode 100644 hw/arm/imx25_3ds.c
 create mode 100644 hw/i2c/imx_i2c.c
 create mode 100644 hw/i2c/imx_i2c_regs.h
 create mode 100644 hw/net/imx_fec.c
 create mode 100644 tests/ds1338-test.c
 create mode 100644 tests/libqos/i2c-imx.c

-- 
1.8.1.2

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [Qemu-devel] [PATCH v3 1/4] Add i.MX FEC Ethernet emulator
  2013-05-05 14:28 [Qemu-devel] [PATCH v3 0/4] Add i.MX25 support through the 3DS evaluation board Jean-Christophe DUBOIS
@ 2013-05-05 14:28 ` Jean-Christophe DUBOIS
  2013-05-05 14:28 ` [Qemu-devel] [PATCH v3 2/4] Add i.MX I2C controller emulator Jean-Christophe DUBOIS
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 7+ messages in thread
From: Jean-Christophe DUBOIS @ 2013-05-05 14:28 UTC (permalink / raw)
  To: qemu-devel
  Cc: peter.maydell, peter.crosthwaite, peter.chubb, afaerber,
	Jean-Christophe DUBOIS

This is based on the mcf_fec.c FEC implementation for ColdFire.

    * a generic phy was added (borrowed from lan9118).
    * The buffer management is also modified as buffers are
      slightly different between coldfire and i.MX.

Changes since V1:
    * none

Changes since v2:
    * use QOM cast
    * reworked debug printf
    * use CamelCase for state type
    * warn with qemu_log_mask(LOG_GUEST_ERROR) or qemu_log_mask(LOG_UNIMP)
    * move to dma_memory_read/write API
    * rework interrupt handling
    * use qemu_flush_queued_packets() in rx_enable()

Signed-off-by: Jean-Christophe DUBOIS <jcd@tribudubois.net>
---
 hw/net/Makefile.objs |   1 +
 hw/net/imx_fec.c     | 793 +++++++++++++++++++++++++++++++++++++++++++++++++++
 include/hw/arm/imx.h |   1 +
 3 files changed, 795 insertions(+)
 create mode 100644 hw/net/imx_fec.c

diff --git a/hw/net/Makefile.objs b/hw/net/Makefile.objs
index 951cca3..5c84727 100644
--- a/hw/net/Makefile.objs
+++ b/hw/net/Makefile.objs
@@ -18,6 +18,7 @@ common-obj-$(CONFIG_OPENCORES_ETH) += opencores_eth.o
 common-obj-$(CONFIG_XGMAC) += xgmac.o
 common-obj-$(CONFIG_MIPSNET) += mipsnet.o
 common-obj-$(CONFIG_XILINX_AXI) += xilinx_axienet.o
+common-obj-$(CONFIG_IMX_FEC) += imx_fec.o
 
 common-obj-$(CONFIG_CADENCE) += cadence_gem.o
 common-obj-$(CONFIG_STELLARIS_ENET) += stellaris_enet.o
diff --git a/hw/net/imx_fec.c b/hw/net/imx_fec.c
new file mode 100644
index 0000000..aee0a4a
--- /dev/null
+++ b/hw/net/imx_fec.c
@@ -0,0 +1,793 @@
+/*
+ * i.MX Fast Ethernet Controller emulation.
+ *
+ * Copyright (c) 2013 Jean-Christophe Dubois.
+ *
+ * Based on Coldfire Fast Ethernet Controller emulation.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ *
+ * This code is licensed under the GPL
+ */
+
+#include "qemu/bitops.h"
+
+#include "sysemu/dma.h"
+
+#include "net/net.h"
+
+#include "hw/sysbus.h"
+
+/* For crc32 */
+#include <zlib.h>
+
+#include "hw/arm/imx.h"
+
+#ifndef IMX_FEC_DEBUG
+#define IMX_FEC_DEBUG          0
+#endif
+
+#ifndef IMX_PHY_DEBUG
+#define IMX_PHY_DEBUG          0
+#endif
+
+#if IMX_FEC_DEBUG
+#define FEC_PRINTF(fmt, ...) \
+    do { fprintf(stderr, "imx_fec[%s]: " fmt , __func__, ## __VA_ARGS__); \
+       } while (0)
+#else
+#define FEC_PRINTF(fmt, ...) do {} while (0)
+#endif
+
+#if IMX_PHY_DEBUG
+#define PHY_PRINTF(fmt, ...) \
+    do { fprintf(stderr, "imx_fec_phy[%s]: " fmt , __func__, ## __VA_ARGS__); \
+       } while (0)
+#else
+#define PHY_PRINTF(fmt, ...) do {} while (0)
+#endif
+
+#define TYPE_IMX_FEC                  "imx.fec"
+#define IMX_FEC(obj)                  \
+    OBJECT_CHECK(IMXFECState, (obj), TYPE_IMX_FEC)
+
+#define FEC_MAX_FRAME_SIZE 2032
+
+typedef struct {
+    SysBusDevice parent_obj;
+
+    NICState *nic;
+    NICConf conf;
+    qemu_irq irq;
+    MemoryRegion iomem;
+
+    uint32_t irq_state;
+    uint32_t eir;
+    uint32_t eimr;
+    uint32_t rx_enabled;
+    uint32_t rx_descriptor;
+    uint32_t tx_descriptor;
+    uint32_t ecr;
+    uint32_t mmfr;
+    uint32_t mscr;
+    uint32_t mibc;
+    uint32_t rcr;
+    uint32_t tcr;
+    uint32_t tfwr;
+    uint32_t frsr;
+    uint32_t erdsr;
+    uint32_t etdsr;
+    uint32_t emrbr;
+    uint32_t miigsk_cfgr;
+    uint32_t miigsk_enr;
+
+    uint32_t phy_status;
+    uint32_t phy_control;
+    uint32_t phy_advertise;
+    uint32_t phy_int;
+    uint32_t phy_int_mask;
+} IMXFECState;
+
+static const VMStateDescription vmstate_imx_fec = {
+    .name = TYPE_IMX_FEC,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(irq_state, IMXFECState),
+        VMSTATE_UINT32(eir, IMXFECState),
+        VMSTATE_UINT32(eimr, IMXFECState),
+        VMSTATE_UINT32(rx_enabled, IMXFECState),
+        VMSTATE_UINT32(rx_descriptor, IMXFECState),
+        VMSTATE_UINT32(tx_descriptor, IMXFECState),
+        VMSTATE_UINT32(ecr, IMXFECState),
+        VMSTATE_UINT32(mmfr, IMXFECState),
+        VMSTATE_UINT32(mscr, IMXFECState),
+        VMSTATE_UINT32(mibc, IMXFECState),
+        VMSTATE_UINT32(rcr, IMXFECState),
+        VMSTATE_UINT32(tcr, IMXFECState),
+        VMSTATE_UINT32(tfwr, IMXFECState),
+        VMSTATE_UINT32(frsr, IMXFECState),
+        VMSTATE_UINT32(erdsr, IMXFECState),
+        VMSTATE_UINT32(etdsr, IMXFECState),
+        VMSTATE_UINT32(emrbr, IMXFECState),
+        VMSTATE_UINT32(miigsk_cfgr, IMXFECState),
+        VMSTATE_UINT32(miigsk_enr, IMXFECState),
+
+        VMSTATE_UINT32(phy_status, IMXFECState),
+        VMSTATE_UINT32(phy_control, IMXFECState),
+        VMSTATE_UINT32(phy_advertise, IMXFECState),
+        VMSTATE_UINT32(phy_int, IMXFECState),
+        VMSTATE_UINT32(phy_int_mask, IMXFECState),
+
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+#define PHY_INT_ENERGYON            (1 << 7)
+#define PHY_INT_AUTONEG_COMPLETE    (1 << 6)
+#define PHY_INT_FAULT               (1 << 5)
+#define PHY_INT_DOWN                (1 << 4)
+#define PHY_INT_AUTONEG_LP          (1 << 3)
+#define PHY_INT_PARFAULT            (1 << 2)
+#define PHY_INT_AUTONEG_PAGE        (1 << 1)
+
+static void imx_fec_update(IMXFECState *s);
+
+/*
+ * The MII phy could raise a GPIO to the guest which in turn
+ * could be handled as an interrupt by the guest.
+ * For now we don't handle any GPIO/interrupt line, so the guest will
+ * have to poll for the PHY status.
+ */
+static void phy_update_irq(IMXFECState *s)
+{
+    imx_fec_update(s);
+}
+
+static void phy_update_link(IMXFECState *s)
+{
+    /* Autonegotiation status mirrors link status.  */
+    if (qemu_get_queue(s->nic)->link_down) {
+        PHY_PRINTF("link is down\n");
+        s->phy_status &= ~0x0024;
+        s->phy_int |= PHY_INT_DOWN;
+    } else {
+        PHY_PRINTF("link is up\n");
+        s->phy_status |= 0x0024;
+        s->phy_int |= PHY_INT_ENERGYON;
+        s->phy_int |= PHY_INT_AUTONEG_COMPLETE;
+    }
+    phy_update_irq(s);
+}
+
+static void imx_fec_set_link(NetClientState *nc)
+{
+    phy_update_link(IMX_FEC(qemu_get_nic_opaque(nc)));
+}
+
+static void phy_reset(IMXFECState *s)
+{
+    PHY_PRINTF("\n");
+
+    s->phy_status = 0x7809;
+    s->phy_control = 0x3000;
+    s->phy_advertise = 0x01e1;
+    s->phy_int_mask = 0;
+    s->phy_int = 0;
+    phy_update_link(s);
+}
+
+static uint32_t do_phy_read(IMXFECState *s, int reg)
+{
+    uint32_t val;
+
+    switch (reg) {
+    case 0:     /* Basic Control */
+        val = s->phy_control;
+        break;
+    case 1:     /* Basic Status */
+        val = s->phy_status;
+        break;
+    case 2:     /* ID1 */
+        val = 0x0007;
+        break;
+    case 3:     /* ID2 */
+        val = 0xc0d1;
+        break;
+    case 4:     /* Auto-neg advertisement */
+        val = s->phy_advertise;
+        break;
+    case 5:     /* Auto-neg Link Partner Ability */
+        val = 0x0f71;
+        break;
+    case 6:     /* Auto-neg Expansion */
+        val = 1;
+        break;
+    case 17:
+    case 18:
+    case 27:
+    case 31:
+        /* TODO 17, 18, 27, 31 */
+        qemu_log_mask(LOG_UNIMP, "%s: PHY reg %d not implemented\n",
+                      __func__, reg);
+        val = 0;
+        break;
+    case 29:    /* Interrupt source.  */
+        val = s->phy_int;
+        s->phy_int = 0;
+        phy_update_irq(s);
+        break;
+    case 30:    /* Interrupt mask */
+        val = s->phy_int_mask;
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: PHY reg %d is not expected\n",
+                      __func__, reg);
+        val = 0;
+        break;
+    }
+
+    PHY_PRINTF("PHY read reg %d = %04x\n", reg, val);
+
+    return val;
+}
+
+static void do_phy_write(IMXFECState *s, int reg, uint32_t val)
+{
+    PHY_PRINTF("PHY write reg %d = %04x\n", reg, val);
+
+    switch (reg) {
+    case 0:     /* Basic Control */
+        if (val & 0x8000) {
+            phy_reset(s);
+        } else {
+            s->phy_control = val & 0x7980;
+            /* Complete autonegotiation immediately.  */
+            if (val & 0x1000) {
+                s->phy_status |= 0x0020;
+            }
+        }
+        break;
+    case 4:     /* Auto-neg advertisement */
+        s->phy_advertise = (val & 0x2d7f) | 0x80;
+        break;
+    case 17:
+    case 18:
+    case 27:
+    case 31:
+        /* TODO 17, 18, 27, 31 */
+        qemu_log_mask(LOG_UNIMP, "%s: PHY reg %d not implemented\n",
+                      __func__, reg);
+        break;
+    case 30:    /* Interrupt mask */
+        s->phy_int_mask = val & 0xff;
+        phy_update_irq(s);
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR, "%s: PHY reg %d is not expected\n",
+                      __func__, reg);
+    }
+}
+
+#define FEC_INT_HB      (1 << 31)
+#define FEC_INT_BABR    (1 << 30)
+#define FEC_INT_BABT    (1 << 29)
+#define FEC_INT_GRA     (1 << 28)
+#define FEC_INT_TXF     (1 << 27)
+#define FEC_INT_TXB     (1 << 26)
+#define FEC_INT_RXF     (1 << 25)
+#define FEC_INT_RXB     (1 << 24)
+#define FEC_INT_MII     (1 << 23)
+#define FEC_INT_EBERR   (1 << 22)
+#define FEC_INT_LC      (1 << 21)
+#define FEC_INT_RL      (1 << 20)
+#define FEC_INT_UN      (1 << 19)
+
+#define FEC_EN      2
+#define FEC_RESET   1
+
+/* Buffer Descriptor.  */
+typedef struct {
+    uint16_t length;
+    uint16_t flags;
+    uint32_t data;
+} imx_fec_bd;
+
+#define FEC_BD_R    (1 << 15)
+#define FEC_BD_E    (1 << 15)
+#define FEC_BD_O1   (1 << 14)
+#define FEC_BD_W    (1 << 13)
+#define FEC_BD_O2   (1 << 12)
+#define FEC_BD_L    (1 << 11)
+#define FEC_BD_TC   (1 << 10)
+#define FEC_BD_ABC  (1 << 9)
+#define FEC_BD_M    (1 << 8)
+#define FEC_BD_BC   (1 << 7)
+#define FEC_BD_MC   (1 << 6)
+#define FEC_BD_LG   (1 << 5)
+#define FEC_BD_NO   (1 << 4)
+#define FEC_BD_CR   (1 << 2)
+#define FEC_BD_OV   (1 << 1)
+#define FEC_BD_TR   (1 << 0)
+
+static void imx_fec_read_bd(imx_fec_bd *bd, dma_addr_t addr)
+{
+    dma_memory_read(&dma_context_memory, addr, bd, sizeof(*bd));
+}
+
+static void imx_fec_write_bd(imx_fec_bd *bd, dma_addr_t addr)
+{
+    dma_memory_write(&dma_context_memory, addr, bd, sizeof(*bd));
+}
+
+static void imx_fec_update(IMXFECState *s)
+{
+    uint32_t active;
+    uint32_t changed;
+
+    active = s->eir & s->eimr;
+    changed = active ^ s->irq_state;
+    if (changed) {
+        qemu_set_irq(s->irq, active);
+    }
+    s->irq_state = active;
+}
+
+static void imx_fec_do_tx(IMXFECState *s)
+{
+    int frame_size = 0;
+    uint8_t frame[FEC_MAX_FRAME_SIZE];
+    uint8_t *ptr = frame;
+    uint32_t addr = s->tx_descriptor;
+
+    FEC_PRINTF("\n");
+
+    while (1) {
+        imx_fec_bd bd;
+        int len;
+
+        imx_fec_read_bd(&bd, addr);
+        FEC_PRINTF("tx_bd %x flags %04x len %d data %08x\n",
+                    addr, bd.flags, bd.length, bd.data);
+        if ((bd.flags & FEC_BD_R) == 0) {
+            /* Run out of descriptors to transmit.  */
+            break;
+        }
+        len = bd.length;
+        if (frame_size + len > FEC_MAX_FRAME_SIZE) {
+            len = FEC_MAX_FRAME_SIZE - frame_size;
+            s->eir |= FEC_INT_BABT;
+        }
+        cpu_physical_memory_read(bd.data, ptr, len);
+        ptr += len;
+        frame_size += len;
+        if (bd.flags & FEC_BD_L) {
+            /* Last buffer in frame.  */
+            FEC_PRINTF("Sending packet\n");
+            qemu_send_packet(qemu_get_queue(s->nic), frame, len);
+            ptr = frame;
+            frame_size = 0;
+            s->eir |= FEC_INT_TXF;
+        }
+        s->eir |= FEC_INT_TXB;
+        bd.flags &= ~FEC_BD_R;
+        /* Write back the modified descriptor.  */
+        imx_fec_write_bd(&bd, addr);
+        /* Advance to the next descriptor.  */
+        if ((bd.flags & FEC_BD_W) != 0) {
+            addr = s->etdsr;
+        } else {
+            addr += 8;
+        }
+    }
+
+    s->tx_descriptor = addr;
+
+    imx_fec_update(s);
+}
+
+static void imx_fec_enable_rx(IMXFECState *s)
+{
+    imx_fec_bd bd;
+    uint32_t tmp;
+
+    imx_fec_read_bd(&bd, s->rx_descriptor);
+
+    tmp = ((bd.flags & FEC_BD_E) != 0);
+
+    if (!tmp) {
+        FEC_PRINTF("RX buffer full\n");
+    } else if (!s->rx_enabled) {
+        qemu_flush_queued_packets(qemu_get_queue(s->nic));
+    }
+
+    s->rx_enabled = tmp;
+}
+
+static void imx_fec_reset(DeviceState *d)
+{
+    IMXFECState *s = IMX_FEC(d);
+
+    /* Reset the FEC */
+    s->eir = 0;
+    s->eimr = 0;
+    s->rx_enabled = 0;
+    s->ecr = 0;
+    s->mscr = 0;
+    s->mibc = 0xc0000000;
+    s->rcr = 0x05ee0001;
+    s->tcr = 0;
+    s->tfwr = 0;
+    s->frsr = 0x500;
+    s->miigsk_cfgr = 0;
+    s->miigsk_enr = 0x6;
+
+    /* We also reset the PHY */
+    phy_reset(s);
+}
+
+static uint64_t imx_fec_read(void *opaque, hwaddr addr, unsigned size)
+{
+    IMXFECState *s = IMX_FEC(opaque);
+
+    FEC_PRINTF("reading from @ 0x%x\n", (int)addr);
+
+    switch (addr & 0x3ff) {
+    case 0x004:
+        return s->eir;
+    case 0x008:
+        return s->eimr;
+    case 0x010:
+        return s->rx_enabled ? (1 << 24) : 0;   /* RDAR */
+    case 0x014:
+        return 0;   /* TDAR */
+    case 0x024:
+        return s->ecr;
+    case 0x040:
+        return s->mmfr;
+    case 0x044:
+        return s->mscr;
+    case 0x064:
+        return s->mibc; /* MIBC */
+    case 0x084:
+        return s->rcr;
+    case 0x0c4:
+        return s->tcr;
+    case 0x0e4:     /* PALR */
+        return (s->conf.macaddr.a[0] << 24) 
+                | (s->conf.macaddr.a[1] << 16)
+                | (s->conf.macaddr.a[2] << 8) 
+                | s->conf.macaddr.a[3];
+        break;
+    case 0x0e8:     /* PAUR */
+        return (s->conf.macaddr.a[4] << 24) 
+                | (s->conf.macaddr.a[5] << 16) 
+                | 0x8808;
+    case 0x0ec:
+        return 0x10000; /* OPD */
+    case 0x118:
+        return 0;
+    case 0x11c:
+        return 0;
+    case 0x120:
+        return 0;
+    case 0x124:
+        return 0;
+    case 0x144:
+        return s->tfwr;
+    case 0x14c:
+        return 0x600;
+    case 0x150:
+        return s->frsr;
+    case 0x180:
+        return s->erdsr;
+    case 0x184:
+        return s->etdsr;
+    case 0x188:
+        return s->emrbr;
+    case 0x300:
+        return s->miigsk_cfgr;
+    case 0x308:
+        return s->miigsk_enr;
+    default:
+        hw_error("imx_fec_read: Bad address 0x%x\n", (int)addr);
+        return 0;
+    }
+}
+
+static void imx_fec_write(void *opaque, hwaddr addr,
+                          uint64_t value, unsigned size)
+{
+    IMXFECState *s = IMX_FEC(opaque);
+
+    FEC_PRINTF("writing 0x%x @ 0x%x\n", (int)value, (int)addr);
+
+    switch (addr & 0x3ff) {
+    case 0x004: /* EIR */
+        s->eir &= ~value;
+        break;
+    case 0x008: /* EIMR */
+        s->eimr = value;
+        break;
+    case 0x010: /* RDAR */
+        if ((s->ecr & FEC_EN) && !s->rx_enabled) {
+            imx_fec_enable_rx(s);
+        }
+        break;
+    case 0x014: /* TDAR */
+        if (s->ecr & FEC_EN) {
+            imx_fec_do_tx(s);
+        }
+        break;
+    case 0x024: /* ECR */
+        s->ecr = value;
+        if (value & FEC_RESET) {
+            imx_fec_reset(&s->parent_obj.qdev);
+        }
+        if ((s->ecr & FEC_EN) == 0) {
+            s->rx_enabled = 0;
+        }
+        break;
+    case 0x040: /* MMFR */
+        /* store the value */
+        s->mmfr = value;
+        if (extract32(value, 28, 1)) {
+            do_phy_write(s, extract32(value, 18, 9), extract32(value, 0, 16));
+        } else {
+            s->mmfr = do_phy_read(s, extract32(value, 18, 9));
+        }
+        /* raise the interrupt as the PHY operation is done */
+        s->eir |= FEC_INT_MII;
+        break;
+    case 0x044: /* MSCR */
+        s->mscr = value & 0xfe;
+        break;
+    case 0x064: /* MIBC */
+        /* TODO: Implement MIB.  */
+        s->mibc = (value & 0x80000000) ? 0xc0000000 : 0;
+        break;
+    case 0x084: /* RCR */
+        s->rcr = value & 0x07ff003f;
+        /* TODO: Implement LOOP mode.  */
+        break;
+    case 0x0c4: /* TCR */
+        /* We transmit immediately, so raise GRA immediately.  */
+        s->tcr = value;
+        if (value & 1) {
+            s->eir |= FEC_INT_GRA;
+        }
+        break;
+    case 0x0e4: /* PALR */
+        s->conf.macaddr.a[0] = value >> 24;
+        s->conf.macaddr.a[1] = value >> 16;
+        s->conf.macaddr.a[2] = value >> 8;
+        s->conf.macaddr.a[3] = value;
+        break;
+    case 0x0e8: /* PAUR */
+        s->conf.macaddr.a[4] = value >> 24;
+        s->conf.macaddr.a[5] = value >> 16;
+        break;
+    case 0x0ec: /* OPDR */
+        break;
+    case 0x118: /* IAUR */
+    case 0x11c: /* IALR */
+    case 0x120: /* GAUR */
+    case 0x124: /* GALR */
+        /* TODO: implement MAC hash filtering.  */
+        break;
+    case 0x144: /* TFWR */
+        s->tfwr = value & 3;
+        break;
+    case 0x14c: /* FRBR */
+        /* FRBR writes ignored.  */
+        break;
+    case 0x150: /* FRSR */
+        s->frsr = (value & 0x3fc) | 0x400;
+        break;
+    case 0x180: /* ERDSR */
+        s->erdsr = value & ~3;
+        s->rx_descriptor = s->erdsr;
+        break;
+    case 0x184: /* ETDSR */
+        s->etdsr = value & ~3;
+        s->tx_descriptor = s->etdsr;
+        break;
+    case 0x188: /* EMRBR */
+        s->emrbr = value & 0x7f0;
+        break;
+    case 0x300: /* MIIGSK_CFGR */
+        s->miigsk_cfgr = value & 0x53;
+        break;
+    case 0x308: /* MIIGSK_ENR */
+        s->miigsk_enr = (value & 0x2) ? 0x6 : 0;
+        break;
+    default:
+        hw_error("imx_fec_write Bad address 0x%x\n", (int)addr);
+    }
+    imx_fec_update(s);
+}
+
+static int imx_fec_can_receive(NetClientState *nc)
+{
+    IMXFECState *s = IMX_FEC(qemu_get_nic_opaque(nc));
+
+    return s->rx_enabled;
+}
+
+static ssize_t imx_fec_receive(NetClientState *nc, const uint8_t *buf,
+                               size_t len)
+{
+    IMXFECState *s = IMX_FEC(qemu_get_nic_opaque(nc));
+    imx_fec_bd bd;
+    uint32_t flags = 0;
+    uint32_t addr;
+    uint32_t crc;
+    uint32_t buf_addr;
+    uint8_t *crc_ptr;
+    unsigned int buf_len;
+    size_t size = len;
+
+    FEC_PRINTF("len %d\n", (int)size);
+
+    if (!s->rx_enabled) {
+        FEC_PRINTF("Unexpected packet\n");
+        return 0;
+    }
+    /* 4 bytes for the CRC.  */
+    size += 4;
+    crc = cpu_to_be32(crc32(~0, buf, size));
+    crc_ptr = (uint8_t *) &crc;
+    /* Huge frames are truncted.  */
+    if (size > FEC_MAX_FRAME_SIZE) {
+        size = FEC_MAX_FRAME_SIZE;
+        flags |= FEC_BD_TR | FEC_BD_LG;
+    }
+    /* Frames larger than the user limit just set error flags.  */
+    if (size > (s->rcr >> 16)) {
+        flags |= FEC_BD_LG;
+    }
+    addr = s->rx_descriptor;
+    while (size > 0) {
+        imx_fec_read_bd(&bd, addr);
+        if ((bd.flags & FEC_BD_E) == 0) {
+            /* No descriptors available.  Bail out.  */
+            /*
+             * FIXME: This is wrong. We should probably either
+             * save the remainder for when more RX buffers are
+             * available, or flag an error.
+             */
+            FEC_PRINTF("Lost end of frame\n");
+            break;
+        }
+        buf_len = (size <= s->emrbr) ? size : s->emrbr;
+        bd.length = buf_len;
+        size -= buf_len;
+        FEC_PRINTF("rx_bd %x length %d\n", addr, bd.length);
+        /* The last 4 bytes are the CRC.  */
+        if (size < 4) {
+            buf_len += size - 4;
+        }
+        buf_addr = bd.data;
+        cpu_physical_memory_write(buf_addr, buf, buf_len);
+        buf += buf_len;
+        if (size < 4) {
+            cpu_physical_memory_write(buf_addr + buf_len, crc_ptr,
+                                      4 - size);
+            crc_ptr += 4 - size;
+        }
+        bd.flags &= ~FEC_BD_E;
+        if (size == 0) {
+            /* Last buffer in frame.  */
+            bd.flags |= flags | FEC_BD_L;
+            FEC_PRINTF("rx frame flags %04x\n", bd.flags);
+            s->eir |= FEC_INT_RXF;
+        } else {
+            s->eir |= FEC_INT_RXB;
+        }
+        imx_fec_write_bd(&bd, addr);
+        /* Advance to the next descriptor.  */
+        if ((bd.flags & FEC_BD_W) != 0) {
+            addr = s->erdsr;
+        } else {
+            addr += 8;
+        }
+    }
+    s->rx_descriptor = addr;
+    imx_fec_enable_rx(s);
+    imx_fec_update(s);
+    return len;
+}
+
+static const MemoryRegionOps imx_fec_ops = {
+    .read = imx_fec_read,
+    .write = imx_fec_write,
+    .valid.min_access_size = 4,
+    .valid.max_access_size = 4,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void imx_fec_cleanup(NetClientState *nc)
+{
+    IMXFECState *s = IMX_FEC(qemu_get_nic_opaque(nc));
+
+    s->nic = NULL;
+}
+
+static NetClientInfo net_imx_fec_info = {
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
+    .size = sizeof(NICState),
+    .can_receive = imx_fec_can_receive,
+    .receive = imx_fec_receive,
+    .cleanup = imx_fec_cleanup,
+    .link_status_changed = imx_fec_set_link,
+};
+
+static int imx_fec_init(SysBusDevice *dev)
+{
+    IMXFECState *s = IMX_FEC(dev);
+
+    memory_region_init_io(&s->iomem, &imx_fec_ops, s, "fec_mmio", 0x400);
+    sysbus_init_mmio(dev, &s->iomem);
+    sysbus_init_irq(dev, &s->irq);
+    qemu_macaddr_default_if_unset(&s->conf.macaddr);
+
+    s->conf.peers.ncs[0] = nd_table[0].netdev;
+
+    s->nic = qemu_new_nic(&net_imx_fec_info, &s->conf,
+                          object_get_typename(OBJECT(dev)), dev->qdev.id, s);
+    qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
+
+    return 0;
+}
+
+static Property imx_fec_properties[] = {
+    DEFINE_NIC_PROPERTIES(IMXFECState, conf),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void imx_fec_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+    k->init = imx_fec_init;
+    dc->reset = imx_fec_reset;
+    dc->vmsd = &vmstate_imx_fec;
+    dc->props = imx_fec_properties;
+}
+
+static const TypeInfo imx_fec_info = {
+    .name = TYPE_IMX_FEC,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(IMXFECState),
+    .class_init = imx_fec_class_init,
+};
+
+static void imx_fec_register_types(void)
+{
+    type_register_static(&imx_fec_info);
+}
+
+void imx_fec_create(int nic, const hwaddr base, qemu_irq irq)
+{
+    NICInfo *nd;
+    DeviceState *dev;
+    SysBusDevice *sbd;
+
+    if (nic >= MAX_NICS) {
+        hw_error("Cannot assign nic %d: QEMU supports only %d ports\n",
+                 nic, MAX_NICS);
+    }
+
+    nd = &nd_table[nic];
+
+    qemu_check_nic_model(nd, TYPE_IMX_FEC);
+    dev = qdev_create(NULL, TYPE_IMX_FEC);
+    qdev_set_nic_properties(dev, nd);
+    qdev_init_nofail(dev);
+    sbd = SYS_BUS_DEVICE(dev);
+    sysbus_mmio_map(sbd, 0, base);
+    sysbus_connect_irq(sbd, 0, irq);
+};
+
+type_init(imx_fec_register_types)
diff --git a/include/hw/arm/imx.h b/include/hw/arm/imx.h
index ea9e093..a875171 100644
--- a/include/hw/arm/imx.h
+++ b/include/hw/arm/imx.h
@@ -30,5 +30,6 @@ void imx_timerg_create(const hwaddr addr,
                       qemu_irq irq,
                       DeviceState *ccm);
 
+void imx_fec_create(int nic, const hwaddr base, qemu_irq irq);
 
 #endif /* IMX_H */
-- 
1.8.1.2

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [Qemu-devel] [PATCH v3 2/4] Add i.MX I2C controller emulator
  2013-05-05 14:28 [Qemu-devel] [PATCH v3 0/4] Add i.MX25 support through the 3DS evaluation board Jean-Christophe DUBOIS
  2013-05-05 14:28 ` [Qemu-devel] [PATCH v3 1/4] Add i.MX FEC Ethernet emulator Jean-Christophe DUBOIS
@ 2013-05-05 14:28 ` Jean-Christophe DUBOIS
  2013-05-06  2:19   ` Peter Crosthwaite
  2013-05-05 14:29 ` [Qemu-devel] [PATCH v3 3/4] Add i.MX25 3DS evaluation board support Jean-Christophe DUBOIS
  2013-05-05 14:29 ` [Qemu-devel] [PATCH v3 4/4] Add qtest support for i.MX I2C device emulation Jean-Christophe DUBOIS
  3 siblings, 1 reply; 7+ messages in thread
From: Jean-Christophe DUBOIS @ 2013-05-05 14:28 UTC (permalink / raw)
  To: qemu-devel
  Cc: peter.maydell, peter.crosthwaite, peter.chubb, afaerber,
	Jean-Christophe DUBOIS

The slave mode is not implemented.

Changes since v1:
    * use QOM cast
    * run checkpatch on code
    * added restrictin on MemoryRegionOps
    * use DeviceClass::realise as init function

Changes since v2:
    * use CamelCase for state type
    * use extrac32() for bit manipilation.
    * improve QOM cast
    * separate regs definition in its own file (to be reused by qtest)

Signed-off-by: Jean-Christophe DUBOIS <jcd@tribudubois.net>
---
 hw/i2c/Makefile.objs  |   1 +
 hw/i2c/imx_i2c.c      | 352 ++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/i2c/imx_i2c_regs.h |  63 +++++++++
 3 files changed, 416 insertions(+)
 create mode 100644 hw/i2c/imx_i2c.c
 create mode 100644 hw/i2c/imx_i2c_regs.h

diff --git a/hw/i2c/Makefile.objs b/hw/i2c/Makefile.objs
index 648278e..d27bbaa 100644
--- a/hw/i2c/Makefile.objs
+++ b/hw/i2c/Makefile.objs
@@ -4,4 +4,5 @@ common-obj-$(CONFIG_ACPI) += smbus_ich9.o
 common-obj-$(CONFIG_APM) += pm_smbus.o
 common-obj-$(CONFIG_BITBANG_I2C) += bitbang_i2c.o
 common-obj-$(CONFIG_EXYNOS4) += exynos4210_i2c.o
+common-obj-$(CONFIG_IMX_I2C) += imx_i2c.o
 obj-$(CONFIG_OMAP) += omap_i2c.o
diff --git a/hw/i2c/imx_i2c.c b/hw/i2c/imx_i2c.c
new file mode 100644
index 0000000..9debcce
--- /dev/null
+++ b/hw/i2c/imx_i2c.c
@@ -0,0 +1,352 @@
+/*
+ *  i.MX I2C Bus Serial Interface Emulation
+ *
+ *  Copyright (C) 2013 Jean-Christophe Dubois.
+ *
+ *  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 "qemu/bitops.h"
+
+#include "hw/sysbus.h"
+#include "hw/i2c/i2c.h"
+
+#include "hw/i2c/imx_i2c_regs.h"
+
+#ifndef IMX_I2C_DEBUG
+#define IMX_I2C_DEBUG                 0
+#endif
+
+#if IMX_I2C_DEBUG
+#define DPRINT(fmt, ...)              \
+    do { fprintf(stderr, "imx_i2c[%s]: " fmt, __func__, ## __VA_ARGS__); \
+       } while (0)
+
+static const char *imx_i2c_get_regname(unsigned offset)
+{
+    switch (offset) {
+    case IADR_ADDR:
+        return "IADR";
+    case IFDR_ADDR:
+        return "IFDR";
+    case I2CR_ADDR:
+        return "I2CR";
+    case I2SR_ADDR:
+        return "I2SR";
+    case I2DR_ADDR:
+        return "I2DR";
+    default:
+        return "[?]";
+    }
+}
+#else
+#define DPRINT(fmt, args...)              do { } while (0)
+#endif
+
+#define TYPE_IMX_I2C                  "imx.i2c"
+#define IMX_I2C(obj)                  \
+    OBJECT_CHECK(IMXI2CState, (obj), TYPE_IMX_I2C)
+
+typedef struct IMXI2CState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion iomem;
+    i2c_bus *bus;
+    qemu_irq irq;
+
+    uint16_t  address;
+
+    uint16_t iadr;
+    uint16_t ifdr;
+    uint16_t i2cr;
+    uint16_t i2sr;
+    uint16_t i2dr_read;
+    uint16_t i2dr_write;
+} IMXI2CState;
+
+static inline bool imx_i2c_is_enabled(IMXI2CState *s)
+{
+    return s->i2cr & I2CR_IEN;
+}
+
+static inline bool imx_i2c_interrupt_is_enabled(IMXI2CState *s)
+{
+    return s->i2cr & I2CR_IIEN;
+}
+
+static inline bool imx_i2c_is_master(IMXI2CState *s)
+{
+    return s->i2cr & I2CR_MSTA;
+}
+
+static inline bool imx_i2c_direction_is_tx(IMXI2CState *s)
+{
+    return s->i2cr & I2CR_MTX;
+}
+
+static void imx_i2c_reset(DeviceState *dev)
+{
+    IMXI2CState *s = IMX_I2C(dev);
+
+    if (s->address != ADDR_RESET) {
+        i2c_end_transfer(s->bus);
+    }
+
+    s->address    = ADDR_RESET;
+    s->iadr       = IADR_RESET;
+    s->ifdr       = IFDR_RESET;
+    s->i2cr       = I2CR_RESET;
+    s->i2sr       = I2SR_RESET;
+    s->i2dr_read  = I2DR_RESET;
+    s->i2dr_write = I2DR_RESET;
+}
+
+static inline void imx_i2c_raise_interrupt(IMXI2CState *s)
+{
+    /*
+     * raise an interrupt if the device is enabled and it is configured
+     * to generate some interrupts.
+     */
+    if (imx_i2c_is_enabled(s) && imx_i2c_interrupt_is_enabled(s)) {
+        s->i2sr |= I2SR_IIF;
+        qemu_irq_raise(s->irq);
+    }
+}
+
+static uint64_t imx_i2c_read(void *opaque, hwaddr offset,
+                             unsigned size)
+{
+    uint16_t value;
+    IMXI2CState *s = IMX_I2C(opaque);
+
+    switch (offset) {
+    case IADR_ADDR:
+        value = s->iadr;
+        break;
+    case IFDR_ADDR:
+        value = s->ifdr;
+        break;
+    case I2CR_ADDR:
+        value = s->i2cr;
+        break;
+    case I2SR_ADDR:
+        value = s->i2sr;
+        break;
+    case I2DR_ADDR:
+        value = s->i2dr_read;
+
+        if (imx_i2c_is_master(s)) { /* master mode */
+            int ret = 0xff;
+
+            if (s->address == ADDR_RESET) {
+                /* something is wrong as the address is not set */
+                DPRINT("Trying to read without specifying the slave address\n");
+            } else if (s->i2cr & I2CR_MTX) {
+                DPRINT("Trying to read but MTX is set\n");
+            } else {
+                /* get the next byte */
+                ret = i2c_recv(s->bus);
+
+                if (ret >= 0) {
+                    imx_i2c_raise_interrupt(s);
+                } else {
+                    DPRINT("read failed for device 0x%02x\n" s->address);
+                    ret = 0xff;
+                }
+            }
+
+            s->i2dr_read = ret;
+        }
+        break;
+    default:
+        hw_error("%s: Bad address 0x%x\n", __func__, (int)offset);
+        break;
+    }
+
+    DPRINT("read %s [0x%02x] -> 0x%02x\n", imx_i2c_get_regname(offset),
+           offset, value);
+
+    return (uint64_t)value;
+}
+
+static void imx_i2c_write(void *opaque, hwaddr offset,
+                          uint64_t value, unsigned size)
+{
+    IMXI2CState *s = IMX_I2C(opaque);
+
+    DPRINT("write %s [0x%02x] <- 0x%02x\n", imx_i2c_get_regname(offset),
+           offset, (int)value);
+
+    value &= 0xff;
+
+    switch (offset) {
+    case IADR_ADDR:
+        s->iadr = value & IADR_MASK;
+        /* i2c_set_slave_address(s->bus, (uint8_t)s->iadr); */
+        break;
+    case IFDR_ADDR:
+        s->ifdr = value & IFDR_MASK;
+        break;
+    case I2CR_ADDR:
+        if (imx_i2c_is_enabled(s) && ((value & I2CR_IEN) == 0)) {
+            /* This is a soft reset. IADR is preserved during soft resets */
+            uint16_t iadr = s->iadr;
+            imx_i2c_reset(&s->parent_obj.qdev);
+            s->iadr = iadr;
+        } else { /* normal write */
+            s->i2cr = value & I2CR_MASK;
+
+            if (imx_i2c_is_master(s)) { /* master mode */
+                /* set the bus to busy */
+                s->i2sr |= I2SR_IBB;
+            } else { /* slave mode */
+                /* bus is not busy anymore */
+                s->i2sr &= ~I2SR_IBB;
+
+                /*
+                 *if we unset the master mode then it ends the ongoing
+                 * transfer if any
+                 */
+                if (s->address != ADDR_RESET) {
+                    i2c_end_transfer(s->bus);
+                    s->address = ADDR_RESET;
+                }
+            }
+
+            if (s->i2cr & I2CR_RSTA) { /* Restart */
+                /* if this is a restart then it ends the ongoing transfer */
+                if (s->address != ADDR_RESET) {
+                    i2c_end_transfer(s->bus);
+                    s->address = ADDR_RESET;
+                    s->i2cr &= ~I2CR_RSTA;
+                }
+            }
+        }
+        break;
+    case I2SR_ADDR:
+        /*
+         * if the user writes 0 to IIF then lower the interrupt and
+         * reset the bit
+         */
+        if ((s->i2sr & I2SR_IIF) && !(value & I2SR_IIF)) {
+            s->i2sr &= ~I2SR_IIF;
+            qemu_irq_lower(s->irq);
+        }
+
+        /*
+         * if the user writes 0 to IAL, reset the bit
+         */
+        if ((s->i2sr & I2SR_IAL) && !(value & I2SR_IAL)) {
+            s->i2sr &= ~I2SR_IAL;
+        }
+
+        break;
+    case I2DR_ADDR:
+        /* if the device is not enabled, nothing to do */
+        if (!imx_i2c_is_enabled(s)) {
+            break;
+        }
+
+        s->i2dr_write = value & I2DR_MASK;
+
+        if (imx_i2c_is_master(s)) { /* master mode */
+            /* If this is the first write cycle then it is the slave addr */
+            if (s->address == ADDR_RESET) {
+                if (i2c_start_transfer(s->bus, extract32(s->i2dr_write, 1, 7),
+                                       extract32(s->i2dr_write, 0, 1))) {
+                    /* if non zero is returned, the adress is not valid */
+                    s->i2sr |= I2SR_RXAK;
+                } else {
+                    s->address = s->i2dr_write;
+                    s->i2sr &= ~I2SR_RXAK;
+                    imx_i2c_raise_interrupt(s);
+                }
+            } else { /* This is a normal data write */
+                if (i2c_send(s->bus, s->i2dr_write)) {
+                    /* if the target return non zero then end the transfer */
+                    s->i2sr |= I2SR_RXAK;
+                    s->address = ADDR_RESET;
+                    i2c_end_transfer(s->bus);
+                } else {
+                    s->i2sr &= ~I2SR_RXAK;
+                    imx_i2c_raise_interrupt(s);
+                }
+            }
+        }
+        break;
+    default:
+        hw_error("%s: Bad address 0x%x\n", __func__, (int)offset);
+        break;
+    }
+}
+
+static const MemoryRegionOps imx_i2c_ops = {
+    .read = imx_i2c_read,
+    .write = imx_i2c_write,
+    .valid.min_access_size = 1,
+    .valid.max_access_size = 2,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription imx_i2c_vmstate = {
+    .name = TYPE_IMX_I2C,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT16(address, IMXI2CState),
+        VMSTATE_UINT16(iadr, IMXI2CState),
+        VMSTATE_UINT16(ifdr, IMXI2CState),
+        VMSTATE_UINT16(i2cr, IMXI2CState),
+        VMSTATE_UINT16(i2sr, IMXI2CState),
+        VMSTATE_UINT16(i2dr_read, IMXI2CState),
+        VMSTATE_UINT16(i2dr_write, IMXI2CState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void imx_i2c_realize(DeviceState *dev, Error **errp)
+{
+    IMXI2CState *s = IMX_I2C(dev);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+    memory_region_init_io(&s->iomem, &imx_i2c_ops, s, TYPE_IMX_I2C,
+                          IMX_I2C_MEM_SIZE);
+    sysbus_init_mmio(sbd, &s->iomem);
+    sysbus_init_irq(sbd, &s->irq);
+    s->bus = i2c_init_bus(DEVICE(dev), "i2c");
+}
+
+static void imx_i2c_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->vmsd = &imx_i2c_vmstate;
+    dc->reset = imx_i2c_reset;
+    dc->realize = imx_i2c_realize;
+}
+
+static const TypeInfo imx_i2c_type_info = {
+    .name = TYPE_IMX_I2C,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(IMXI2CState),
+    .class_init = imx_i2c_class_init,
+};
+
+static void imx_i2c_register_types(void)
+{
+    type_register_static(&imx_i2c_type_info);
+}
+
+type_init(imx_i2c_register_types)
diff --git a/hw/i2c/imx_i2c_regs.h b/hw/i2c/imx_i2c_regs.h
new file mode 100644
index 0000000..95c7cf5
--- /dev/null
+++ b/hw/i2c/imx_i2c_regs.h
@@ -0,0 +1,63 @@
+/*
+ *  i.MX I2C Bus Serial Interface registers definition
+ *
+ *  Copyright (C) 2013 Jean-Christophe Dubois.
+ *
+ *  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/>.
+ *
+ */
+
+#ifndef __IMX_I2C_REGS_H_
+#define __IMX_I2C_REGS_H_
+
+#define IMX_I2C_MEM_SIZE           0x14
+
+/* i.MX I2C memory map */
+#define IADR_ADDR                  0x00  /* address register */
+#define IFDR_ADDR                  0x04  /* frequency divider register */
+#define I2CR_ADDR                  0x08  /* control register */
+#define I2SR_ADDR                  0x0c  /* status register */
+#define I2DR_ADDR                  0x10  /* data register */
+
+#define IADR_MASK                  0xFE
+#define IADR_RESET                 0
+
+#define IFDR_MASK                  0x3F
+#define IFDR_RESET                 0
+
+#define I2CR_IEN                   (1 << 7)
+#define I2CR_IIEN                  (1 << 6)
+#define I2CR_MSTA                  (1 << 5)
+#define I2CR_MTX                   (1 << 4)
+#define I2CR_TXAK                  (1 << 3)
+#define I2CR_RSTA                  (1 << 2)
+#define I2CR_MASK                  0xFC
+#define I2CR_RESET                 0
+
+#define I2SR_ICF                   (1 << 7)
+#define I2SR_IAAF                  (1 << 6)
+#define I2SR_IBB                   (1 << 5)
+#define I2SR_IAL                   (1 << 4)
+#define I2SR_SRW                   (1 << 2)
+#define I2SR_IIF                   (1 << 1)
+#define I2SR_RXAK                  (1 << 0)
+#define I2SR_MASK                  0xE9
+#define I2SR_RESET                 0x81
+
+#define I2DR_MASK                  0xFF
+#define I2DR_RESET                 0
+
+#define ADDR_RESET                 0xFF00
+
+#endif /* __IMX_I2C_REGS_H_ */
-- 
1.8.1.2

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [Qemu-devel] [PATCH v3 3/4] Add i.MX25 3DS evaluation board support.
  2013-05-05 14:28 [Qemu-devel] [PATCH v3 0/4] Add i.MX25 support through the 3DS evaluation board Jean-Christophe DUBOIS
  2013-05-05 14:28 ` [Qemu-devel] [PATCH v3 1/4] Add i.MX FEC Ethernet emulator Jean-Christophe DUBOIS
  2013-05-05 14:28 ` [Qemu-devel] [PATCH v3 2/4] Add i.MX I2C controller emulator Jean-Christophe DUBOIS
@ 2013-05-05 14:29 ` Jean-Christophe DUBOIS
  2013-05-05 14:29 ` [Qemu-devel] [PATCH v3 4/4] Add qtest support for i.MX I2C device emulation Jean-Christophe DUBOIS
  3 siblings, 0 replies; 7+ messages in thread
From: Jean-Christophe DUBOIS @ 2013-05-05 14:29 UTC (permalink / raw)
  To: qemu-devel
  Cc: peter.maydell, peter.crosthwaite, peter.chubb, afaerber,
	Jean-Christophe DUBOIS

For now we support:
    * timers (GPT and EPIT)
    * serial ports (only 2 out of 5 possible)
    * ethernet (through the newly added FEC driver)
    * I2C (through the newly added I2C driver)

A ds1338 I2C RTC chip was added on the first i2c bus to allow
automatic test through qtest. This RTC is not present on the real
board.

Changes since v1:
    * Added a ds1338 I2C device for qtest purpose.

Changes since v2:
    * none

Signed-off-by: Jean-Christophe DUBOIS <jcd@tribudubois.net>
---
 default-configs/arm-softmmu.mak |   3 +
 hw/arm/Makefile.objs            |   1 +
 hw/arm/imx25_3ds.c              | 258 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 262 insertions(+)
 create mode 100644 hw/arm/imx25_3ds.c

diff --git a/default-configs/arm-softmmu.mak b/default-configs/arm-softmmu.mak
index 27cbe3d..a20f112 100644
--- a/default-configs/arm-softmmu.mak
+++ b/default-configs/arm-softmmu.mak
@@ -28,6 +28,7 @@ CONFIG_SSI_SD=y
 CONFIG_SSI_M25P80=y
 CONFIG_LAN9118=y
 CONFIG_SMC91C111=y
+CONFIG_IMX_FEC=y
 CONFIG_DS1338=y
 CONFIG_PFLASH_CFI01=y
 CONFIG_PFLASH_CFI02=y
@@ -80,3 +81,5 @@ CONFIG_VERSATILE_PCI=y
 CONFIG_VERSATILE_I2C=y
 
 CONFIG_SDHCI=y
+
+CONFIG_IMX_I2C=y
diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
index 9e3a06f..2f4280d 100644
--- a/hw/arm/Makefile.objs
+++ b/hw/arm/Makefile.objs
@@ -2,6 +2,7 @@ obj-y += boot.o collie.o exynos4_boards.o gumstix.o highbank.o
 obj-y += integratorcp.o kzm.o mainstone.o musicpal.o nseries.o
 obj-y += omap_sx1.o palm.o pic_cpu.o realview.o spitz.o stellaris.o
 obj-y += tosa.o versatilepb.o vexpress.o xilinx_zynq.o z2.o
+obj-y += imx25_3ds.o
 
 obj-y += armv7m.o exynos4210.o pxa2xx.o pxa2xx_gpio.o pxa2xx_pic.o
 obj-y += omap1.o omap2.o strongarm.o
diff --git a/hw/arm/imx25_3ds.c b/hw/arm/imx25_3ds.c
new file mode 100644
index 0000000..b7ff26d
--- /dev/null
+++ b/hw/arm/imx25_3ds.c
@@ -0,0 +1,258 @@
+/*
+ * Copyright (c) 2013 Jean-Christophe Dubois
+ *
+ * 3Dstack Board System emulation.
+ *
+ * Based on hw/arm/kzm.c
+ *
+ * Copyright (c) 2008 OKL and 2011 NICTA
+ * Written by Hans at OK-Labs
+ * Updated by Peter Chubb.
+ *
+ * This code is licensed under the GPL, version 2 or later.
+ * See the file `COPYING' in the top level directory.
+ *
+ * It (partially) emulates a i.MX25 3D-Stack PDK board
+ */
+
+#include "hw/sysbus.h"
+#include "exec/address-spaces.h"
+#include "hw/hw.h"
+#include "hw/arm/arm.h"
+#include "hw/devices.h"
+#include "net/net.h"
+#include "sysemu/sysemu.h"
+#include "hw/boards.h"
+#include "hw/char/serial.h"
+#include "hw/arm/imx.h"
+#include "hw/i2c/i2c.h"
+
+#include "sysemu/qtest.h"
+
+/* Memory map for 3D-Stack Emulation Baseboard:
+ * 0x00000000-0x00003fff 16k ROM              IGNORED
+ * 0x00004000-0x00403fff Reserved             IGNORED
+ * 0x00404000-0x00408fff 20k ROM              IGNORED
+ * 0x00409000-0x0fffffff Reserved             IGNORED
+ * 0x10000000-0x1fffffff Reserved             IGNORED
+ * 0x20000000-0x2fffffff Reserved             IGNORED
+ * 0x30000000-0x3fffffff Reserved             IGNORED
+ * 0x40000000-0x43efffff Reserved             IGNORED
+ * 0x43f00000-0x6fffffff I.MX25 Internal Register Space
+ *   0x43f00000 IO_AREA0
+ *   0x43f80000 I2C0                          EMULATED
+ *   0x43f84000 I2C2                          EMULATED
+ *   0x43f98000 I2C1                          EMULATED
+ *   0x43f90000 UART1                         EMULATED
+ *   0x43f94000 UART2                         EMULATED
+ *   0x43fb0000 UART4                         IGNORED
+ *   0x43fb4000 UART5                         IGNORED
+ *   0x5000c000 UART3                         IGNORED
+ *   0x53f80000 CCM                           EMULATED
+ *   0x53f84000 GPT 4                         EMULATED
+ *   0x53f88000 GPT 3                         EMULATED
+ *   0x53f8c000 GPT 2                         EMULATED
+ *   0x53f90000 GPT 1                         EMULATED
+ *   0x53f94000 PIT 1                         EMULATED
+ *   0x53f98000 PIT 2                         EMULATED
+ *   0x68000000 ASIC                          EMULATED
+ * 0x78000000-0x7801ffff SRAM                 EMULATED
+ * 0x78020000-0x7fffffff SRAM Aliasing        EMULATED
+ * 0x80000000-0x87ffffff RAM + Alias          EMULATED
+ * 0x90000000-0x9fffffff RAM + Alias          EMULATED
+ * 0xa0000000-0xa7ffffff Flash                IGNORED
+ * 0xa8000000-0xafffffff Flash                IGNORED
+ * 0xb0000000-0xb1ffffff SRAM                 IGNORED
+ * 0xb2000000-0xb3ffffff SRAM                 IGNORED
+ * 0xb4000000-0xb5ffffff CS4                  IGNORED
+ * 0xb6000000-0xb8000fff Reserved             IGNORED
+ * 0xb8001000-0xb8001fff SDRAM CTRL reg       IGNORED
+ * 0xb8002000-0xb8002fff WEIM CTRL reg        IGNORED
+ * 0xb8003000-0xb8003fff M3IF CTRL reg        IGNORED
+ * 0xb8004000-0xb8004fff EMI CTRL reg         IGNORED
+ * 0xb8005000-0xbaffffff Reserved             IGNORED
+ * 0xbb000000-0xbb000fff NAND flash area buf  IGNORED
+ * 0xbb001000-0xbb0011ff NAND flash reserved  IGNORED
+ * 0xbb001200-0xbb001dff Reserved             IGNORED
+ * 0xbb001e00-0xbb001fff NAN flash CTRL reg   IGNORED
+ * 0xbb012000-0xbfffffff Reserved             IGNORED
+ * 0xc0000000-0xffffffff Reserved             IGNORED
+ */
+
+#define IMX25_SRAM_ADDRESS  (0x78000000)
+#define IMX25_SRAMSIZE      (128*1024)
+#define IMX25_CS_SRAMSIZE   (128*1024*1024)
+#define IMX25_3DS_ADDRESS   (0x80000000)
+#define IMX25_CS_RAMSIZE    (256*1024*1024)
+
+static struct arm_boot_info imx25_3ds_binfo = {
+    .loader_start = IMX25_3DS_ADDRESS,
+    .board_id = 1771,
+};
+
+static void imx25_3ds_init(QEMUMachineInitArgs *args)
+{
+    int i;
+    ram_addr_t ram_size = args->ram_size;
+    const char *cpu_model = args->cpu_model;
+    const char *kernel_filename = args->kernel_filename;
+    const char *kernel_cmdline = args->kernel_cmdline;
+    const char *initrd_filename = args->initrd_filename;
+    ARMCPU *cpu;
+    MemoryRegion *address_space_mem = get_system_memory();
+    qemu_irq *cpu_pic;
+    DeviceState *pic_dev;
+    DeviceState *ccm;
+    MemoryRegion *sram = g_new(MemoryRegion, 1);
+    MemoryRegion *sram_alias = g_new(MemoryRegion, 1);
+
+    if (!cpu_model) {
+        cpu_model = "arm926";
+    }
+
+    cpu = cpu_arm_init(cpu_model);
+    if (!cpu) {
+        fprintf(stderr, "Unable to find CPU definition\n");
+        exit(1);
+    }
+
+    if (ram_size > (2 * IMX25_CS_RAMSIZE)) {
+        fprintf(stderr, "i.MX25 can support only up to %d MB\n",
+                2 * IMX25_CS_RAMSIZE / (1024 * 1024));
+        exit(1);
+    }
+
+    /* create our main memory */
+    for (i = 0; i <= (ram_size / IMX25_CS_RAMSIZE); i++) {
+        ram_addr_t blk_size = ram_size - (IMX25_CS_RAMSIZE * i);
+        MemoryRegion *ram;
+        char ram_name[20];
+
+        if (blk_size > IMX25_CS_RAMSIZE) {
+            blk_size = IMX25_CS_RAMSIZE;
+        }
+
+        if (blk_size == 0) {
+            break;
+        }
+
+        sprintf(ram_name, "imx25.ram%d", i);
+
+        ram = g_new(MemoryRegion, 1);
+        memory_region_init_ram(ram, ram_name, blk_size);
+        vmstate_register_ram_global(ram);
+        memory_region_add_subregion(address_space_mem, IMX25_3DS_ADDRESS
+                                    + (IMX25_CS_RAMSIZE * i), ram);
+
+        /* Add ram alias */
+        if (blk_size < IMX25_CS_RAMSIZE) {
+            MemoryRegion *ram_alias = g_new(MemoryRegion, 1);
+            char alias_name[20];
+
+            sprintf(alias_name, "ram.alias%d", i);
+
+            memory_region_init_alias(ram_alias, alias_name, ram, 0,
+                                     IMX25_CS_RAMSIZE - blk_size);
+            memory_region_add_subregion(address_space_mem, IMX25_3DS_ADDRESS
+                                        + (IMX25_CS_RAMSIZE * i) + blk_size,
+                                        ram_alias);
+        }
+    }
+
+    /* create the sram area */
+    memory_region_init_ram(sram, "imx25.sram", IMX25_SRAMSIZE);
+    vmstate_register_ram_global(sram);
+    memory_region_add_subregion(address_space_mem, IMX25_SRAM_ADDRESS,
+                                sram);
+
+    /* add sram alias */
+    memory_region_init_alias(sram_alias, "sram.alias", sram, 0,
+                             IMX25_CS_SRAMSIZE - IMX25_SRAMSIZE);
+    memory_region_add_subregion(address_space_mem,
+                                IMX25_SRAM_ADDRESS + IMX25_SRAMSIZE,
+                                sram_alias);
+
+    /* add the PIC */
+    cpu_pic = arm_pic_init_cpu(cpu);
+    pic_dev = sysbus_create_varargs("imx_avic", 0x68000000,
+                                cpu_pic[ARM_PIC_CPU_IRQ],
+                                cpu_pic[ARM_PIC_CPU_FIQ], NULL);
+
+    /* add some serial lines */
+    imx_serial_create(0, 0x43f90000, qdev_get_gpio_in(pic_dev, 45));
+    imx_serial_create(1, 0x43f94000, qdev_get_gpio_in(pic_dev, 32));
+    /*
+     * QEMU does not support more than 4 serial ports. Too bad.
+     */
+    /*
+    imx_serial_create(3, 0x5000c000, qdev_get_gpio_in(pic_dev, 18));
+    imx_serial_create(4, 0x43fb0000, qdev_get_gpio_in(pic_dev, 46));
+    imx_serial_create(5, 0x43fb4000, qdev_get_gpio_in(pic_dev, 47));
+    */
+
+    ccm = sysbus_create_simple("imx_ccm", 0x53f80000, NULL);
+
+    /* add gpt timers */
+    imx_timerg_create(0x53f84000, qdev_get_gpio_in(pic_dev, 1), ccm);
+    imx_timerg_create(0x53f88000, qdev_get_gpio_in(pic_dev, 29), ccm);
+    imx_timerg_create(0x53f8c000, qdev_get_gpio_in(pic_dev, 53), ccm);
+    imx_timerg_create(0x53f90000, qdev_get_gpio_in(pic_dev, 54), ccm);
+
+    /* add epit timers */
+    imx_timerp_create(0x53f94000, qdev_get_gpio_in(pic_dev, 28), ccm);
+    imx_timerp_create(0x53f98000, qdev_get_gpio_in(pic_dev, 27), ccm);
+
+    imx_fec_create(0, 0x50038000, qdev_get_gpio_in(pic_dev, 57));
+
+    /*** I2C ***/
+    for (i = 0; i < 3; i++) {
+        static uint32_t addr[] = {0x43F80000, 0x43F98000, 0x43F84000};
+        static uint32_t irq[]  = {3, 4, 10};
+        SysBusDevice *busdev;
+        DeviceState *i2c_dev = qdev_create(NULL, "imx.i2c");
+
+        qdev_init_nofail(i2c_dev);
+        busdev = SYS_BUS_DEVICE(i2c_dev);
+        sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(pic_dev, irq[i]));
+        sysbus_mmio_map(busdev, 0, addr[i]);
+
+        if (i == 0) {
+            /*
+             * this I2C device doesn't exits on the real board.
+             * We add it to be able to do a bit of simple qtest.
+             * see "make check" for details
+             */
+            i2c_create_slave((i2c_bus *)qdev_get_child_bus(i2c_dev, "i2c"),
+                             "ds1338", 0x68);
+        }
+    }
+
+    imx25_3ds_binfo.ram_size = ram_size;
+    imx25_3ds_binfo.kernel_filename = kernel_filename;
+    imx25_3ds_binfo.kernel_cmdline = kernel_cmdline;
+    imx25_3ds_binfo.initrd_filename = initrd_filename;
+    imx25_3ds_binfo.nb_cpus = 1;
+
+    /*
+     * We test explicitely for qtest here as it is not done (yet?) in
+     * arm_load_kernel(). Without this the "make check" command would
+     * fail.
+     */
+    if (!qtest_enabled()) {
+        arm_load_kernel(cpu, &imx25_3ds_binfo);
+    }
+}
+
+static QEMUMachine imx25_3ds_machine = {
+    .name = "imx25_3ds",
+    .desc = "ARM i.MX25 PDK board (ARM926)",
+    .init = imx25_3ds_init,
+    DEFAULT_MACHINE_OPTIONS,
+};
+
+static void imx25_3ds_machine_init(void)
+{
+    qemu_register_machine(&imx25_3ds_machine);
+}
+
+machine_init(imx25_3ds_machine_init)
-- 
1.8.1.2

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [Qemu-devel] [PATCH v3 4/4] Add qtest support for i.MX I2C device emulation.
  2013-05-05 14:28 [Qemu-devel] [PATCH v3 0/4] Add i.MX25 support through the 3DS evaluation board Jean-Christophe DUBOIS
                   ` (2 preceding siblings ...)
  2013-05-05 14:29 ` [Qemu-devel] [PATCH v3 3/4] Add i.MX25 3DS evaluation board support Jean-Christophe DUBOIS
@ 2013-05-05 14:29 ` Jean-Christophe DUBOIS
  3 siblings, 0 replies; 7+ messages in thread
From: Jean-Christophe DUBOIS @ 2013-05-05 14:29 UTC (permalink / raw)
  To: qemu-devel
  Cc: peter.maydell, peter.crosthwaite, peter.chubb, afaerber,
	Jean-Christophe DUBOIS

This is using a ds1338 RTC chip on the i2c bus. This RTC
chip is not present on the real board.

Changes since v1:
    * not present on v1

Changes since v2:
    * use a common header file for I2C regs definition

Signed-off-by: Jean-Christophe DUBOIS <jcd@tribudubois.net>
---
 tests/Makefile         |   3 +
 tests/ds1338-test.c    |  64 ++++++++++++++++
 tests/libqos/i2c-imx.c | 198 +++++++++++++++++++++++++++++++++++++++++++++++++
 tests/libqos/i2c.h     |   3 +
 4 files changed, 268 insertions(+)
 create mode 100644 tests/ds1338-test.c
 create mode 100644 tests/libqos/i2c-imx.c

diff --git a/tests/Makefile b/tests/Makefile
index bf41d10..5f7a0e0 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -64,6 +64,7 @@ gcov-files-x86_64-y = $(subst i386-softmmu/,x86_64-softmmu/,$(gcov-files-i386-y)
 gcov-files-sparc-y += hw/m48t59.c
 gcov-files-sparc64-y += hw/m48t59.c
 check-qtest-arm-y = tests/tmp105-test$(EXESUF)
+check-qtest-arm-y += tests/ds1338-test$(EXESUF)
 gcov-files-arm-y += hw/tmp105.c
 
 GENERATED_HEADERS += tests/test-qapi-types.h tests/test-qapi-visit.h tests/test-qmp-commands.h
@@ -123,12 +124,14 @@ libqos-obj-y += tests/libqos/i2c.o
 libqos-pc-obj-y = $(libqos-obj-y) tests/libqos/pci-pc.o tests/libqos/fw_cfg-pc.o
 libqos-pc-obj-y += tests/libqos/malloc-pc.o
 libqos-omap-obj-y = $(libqos-obj-y) tests/libqos/i2c-omap.o
+libqos-imx-obj-y = $(libqos-obj-y) tests/libqos/i2c-imx.o
 
 tests/rtc-test$(EXESUF): tests/rtc-test.o
 tests/m48t59-test$(EXESUF): tests/m48t59-test.o
 tests/fdc-test$(EXESUF): tests/fdc-test.o
 tests/hd-geo-test$(EXESUF): tests/hd-geo-test.o
 tests/tmp105-test$(EXESUF): tests/tmp105-test.o $(libqos-omap-obj-y)
+tests/ds1338-test$(EXESUF): tests/ds1338-test.o $(libqos-imx-obj-y)
 tests/i440fx-test$(EXESUF): tests/i440fx-test.o $(libqos-pc-obj-y)
 tests/fw_cfg-test$(EXESUF): tests/fw_cfg-test.o $(libqos-pc-obj-y)
 
diff --git a/tests/ds1338-test.c b/tests/ds1338-test.c
new file mode 100644
index 0000000..3e3fa0b
--- /dev/null
+++ b/tests/ds1338-test.c
@@ -0,0 +1,64 @@
+/*
+ * QTest testcase for the DS1338 RTC
+ *
+ * Copyright (c) 2013 Jean-Christophe Dubois
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include "libqtest.h"
+#include "libqos/i2c.h"
+
+#include <glib.h>
+
+#define IMX25_I2C_0_BASE 0x43F80000
+
+#define DS1338_ADDR 0x68
+
+static I2CAdapter *i2c;
+static uint8_t addr;
+
+#define bcd2bin(x)        (((x) & 0x0f) + ((x) >> 4) * 10)
+
+static void send_and_receive(void)
+{
+    uint8_t cmd[1];
+    uint8_t resp[7];
+    time_t now = time(NULL);
+    struct tm *tm_ptr = localtime(&now);
+
+    /* reset the index in the RTC memory */
+    cmd[0] = 0;
+    i2c_send(i2c, addr, cmd, 1);
+
+    /* retrieve the date */
+    i2c_recv(i2c, addr, resp, 7);
+
+    /* check retreived time againt local time */
+    g_assert_cmpuint(bcd2bin(resp[4]), == , tm_ptr->tm_mday);
+    g_assert_cmpuint(bcd2bin(resp[5]), == , 1 + tm_ptr->tm_mon);
+    g_assert_cmpuint(2000 + bcd2bin(resp[6]), == , 1900 + tm_ptr->tm_year);
+}
+
+int main(int argc, char **argv)
+{
+    QTestState *s = NULL;
+    int ret;
+
+    g_test_init(&argc, &argv, NULL);
+
+    s = qtest_start("-display none -machine imx25_3ds");
+    i2c = imx_i2c_create(IMX25_I2C_0_BASE);
+    addr = DS1338_ADDR;
+
+    qtest_add_func("/ds1338/tx-rx", send_and_receive);
+
+    ret = g_test_run();
+
+    if (s) {
+        qtest_quit(s);
+    }
+    g_free(i2c);
+
+    return ret;
+}
diff --git a/tests/libqos/i2c-imx.c b/tests/libqos/i2c-imx.c
new file mode 100644
index 0000000..173bfc5
--- /dev/null
+++ b/tests/libqos/i2c-imx.c
@@ -0,0 +1,198 @@
+/*
+ * QTest i.MX I2C driver
+ *
+ * Copyright (c) 2013 Jean-Christophe Dubois
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include "libqos/i2c.h"
+
+#include <glib.h>
+#include <string.h>
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+
+#include "hw/i2c/imx_i2c_regs.h"
+
+enum IMXI2CDirection {
+    IMX_I2C_READ,
+    IMX_I2C_WRITE,
+};
+
+typedef struct IMXI2C {
+    I2CAdapter parent;
+
+    uint64_t addr;
+} IMXI2C;
+
+
+static void imx_i2c_set_slave_addr(IMXI2C *s, uint8_t addr,
+                                   enum IMXI2CDirection direction)
+{
+    writeb(s->addr + I2DR_ADDR, (addr << 1) |
+           (direction == IMX_I2C_READ ? 1 : 0));
+}
+
+static void imx_i2c_send(I2CAdapter *i2c, uint8_t addr,
+                         const uint8_t *buf, uint16_t len)
+{
+    IMXI2C *s = (IMXI2C *)i2c;
+    uint8_t data;
+    uint8_t status;
+    uint16_t size = 0;
+
+    if (!len) {
+        return;
+    }
+
+    /* set the bus for write */
+    data = I2CR_IEN |
+           I2CR_IIEN |
+           I2CR_MSTA |
+           I2CR_MTX |
+           I2CR_TXAK;
+
+    writeb(s->addr + I2CR_ADDR, data);
+    status = readb(s->addr + I2SR_ADDR);
+    g_assert((status & I2SR_IBB) != 0);
+
+    /* set the slave address */
+    imx_i2c_set_slave_addr(s, addr, IMX_I2C_WRITE);
+    status = readb(s->addr + I2SR_ADDR);
+    g_assert((status & I2SR_IIF) != 0);
+    g_assert((status & I2SR_RXAK) == 0);
+
+    /* ack the interrupt */
+    writeb(s->addr + I2SR_ADDR, 0);
+    status = readb(s->addr + I2SR_ADDR);
+    g_assert((status & I2SR_IIF) == 0);
+
+    while (size < len) {
+        /* check we are still busy */
+        status = readb(s->addr + I2SR_ADDR);
+        g_assert((status & I2SR_IBB) != 0);
+
+        /* write the data */
+        writeb(s->addr + I2DR_ADDR, buf[size]);
+        status = readb(s->addr + I2SR_ADDR);
+        g_assert((status & I2SR_IIF) != 0);
+        g_assert((status & I2SR_RXAK) == 0);
+
+        /* ack the interrupt */
+        writeb(s->addr + I2SR_ADDR, 0);
+        status = readb(s->addr + I2SR_ADDR);
+        g_assert((status & I2SR_IIF) == 0);
+
+        size++;
+    }
+
+    /* release the bus */
+    data &= ~(I2CR_MSTA | I2CR_MTX);
+    writeb(s->addr + I2CR_ADDR, data);
+    status = readb(s->addr + I2SR_ADDR);
+    g_assert((status & I2SR_IBB) == 0);
+}
+
+static void imx_i2c_recv(I2CAdapter *i2c, uint8_t addr,
+                         uint8_t *buf, uint16_t len)
+{
+    IMXI2C *s = (IMXI2C *)i2c;
+    uint8_t data;
+    uint8_t status;
+    uint16_t size = 0;
+
+    if (!len) {
+        return;
+    }
+
+    /* set the bus for write */
+    data = I2CR_IEN |
+           I2CR_IIEN |
+           I2CR_MSTA |
+           I2CR_MTX |
+           I2CR_TXAK;
+
+    writeb(s->addr + I2CR_ADDR, data);
+    status = readb(s->addr + I2SR_ADDR);
+    g_assert((status & I2SR_IBB) != 0);
+
+    /* set the slave address */
+    imx_i2c_set_slave_addr(s, addr, IMX_I2C_READ);
+    status = readb(s->addr + I2SR_ADDR);
+    g_assert((status & I2SR_IIF) != 0);
+    g_assert((status & I2SR_RXAK) == 0);
+
+    /* ack the interrupt */
+    writeb(s->addr + I2SR_ADDR, 0);
+    status = readb(s->addr + I2SR_ADDR);
+    g_assert((status & I2SR_IIF) == 0);
+
+    /* set the bus for read */
+    data &= ~I2CR_MTX;
+    /* if only one byte don't ack */
+    if (len != 1) {
+        data &= ~I2CR_TXAK;
+    }
+    writeb(s->addr + I2CR_ADDR, data);
+    status = readb(s->addr + I2SR_ADDR);
+    g_assert((status & I2SR_IBB) != 0);
+
+    /* dummy read */
+    readb(s->addr + I2DR_ADDR);
+    status = readb(s->addr + I2SR_ADDR);
+    g_assert((status & I2SR_IIF) != 0);
+
+    /* ack the interrupt */
+    writeb(s->addr + I2SR_ADDR, 0);
+    status = readb(s->addr + I2SR_ADDR);
+    g_assert((status & I2SR_IIF) == 0);
+
+    while (size < len) {
+        /* check we are still busy */
+        status = readb(s->addr + I2SR_ADDR);
+        g_assert((status & I2SR_IBB) != 0);
+
+        if (size == (len - 1)) {
+            /* stop the read transaction */
+            data &= ~(I2CR_MSTA | I2CR_MTX);
+        } else {
+            /* ack the data read */
+            data |= I2CR_TXAK;
+        }
+        writeb(s->addr + I2CR_ADDR, data);
+
+        /* read the data */
+        buf[size] = readb(s->addr + I2DR_ADDR);
+
+        if (size != (len - 1)) {
+            status = readb(s->addr + I2SR_ADDR);
+            g_assert((status & I2SR_IIF) != 0);
+
+            /* ack the interrupt */
+            writeb(s->addr + I2SR_ADDR, 0);
+        }
+
+        status = readb(s->addr + I2SR_ADDR);
+        g_assert((status & I2SR_IIF) == 0);
+
+        size++;
+    }
+
+    status = readb(s->addr + I2SR_ADDR);
+    g_assert((status & I2SR_IBB) == 0);
+}
+
+I2CAdapter *imx_i2c_create(uint64_t addr)
+{
+    IMXI2C *s = g_malloc0(sizeof(*s));
+    I2CAdapter *i2c = (I2CAdapter *)s;
+
+    s->addr = addr;
+
+    i2c->send = imx_i2c_send;
+    i2c->recv = imx_i2c_recv;
+
+    return i2c;
+}
diff --git a/tests/libqos/i2c.h b/tests/libqos/i2c.h
index 1ce9af4..c21f1dc 100644
--- a/tests/libqos/i2c.h
+++ b/tests/libqos/i2c.h
@@ -27,4 +27,7 @@ void i2c_recv(I2CAdapter *i2c, uint8_t addr,
 /* libi2c-omap.c */
 I2CAdapter *omap_i2c_create(uint64_t addr);
 
+/* libi2c-imx.c */
+I2CAdapter *imx_i2c_create(uint64_t addr);
+
 #endif
-- 
1.8.1.2

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [Qemu-devel] [PATCH v3 2/4] Add i.MX I2C controller emulator
  2013-05-05 14:28 ` [Qemu-devel] [PATCH v3 2/4] Add i.MX I2C controller emulator Jean-Christophe DUBOIS
@ 2013-05-06  2:19   ` Peter Crosthwaite
  2013-05-06  6:26     ` Jean-Christophe DUBOIS
  0 siblings, 1 reply; 7+ messages in thread
From: Peter Crosthwaite @ 2013-05-06  2:19 UTC (permalink / raw)
  To: Jean-Christophe DUBOIS; +Cc: peter.maydell, peter.chubb, qemu-devel, afaerber

Hi JC,

On Mon, May 6, 2013 at 12:28 AM, Jean-Christophe DUBOIS
<jcd@tribudubois.net> wrote:
> The slave mode is not implemented.
>
> Changes since v1:
>     * use QOM cast
>     * run checkpatch on code
>     * added restrictin on MemoryRegionOps
>     * use DeviceClass::realise as init function
>
> Changes since v2:
>     * use CamelCase for state type
>     * use extrac32() for bit manipilation.
>     * improve QOM cast
>     * separate regs definition in its own file (to be reused by qtest)
>

Per-patch change logs need to go below the line ...

> Signed-off-by: Jean-Christophe DUBOIS <jcd@tribudubois.net>
> ---

... here. Otherwise it will go into the mainline git log on merge.

The edit can be done by hand edit before the send-email. But I use a
scripted approach personally to move my changelogs out of commit
message pre send (and my working tree has commits formatted similar to
what you have sent here). Others using the manual flow tend to just
use cover letter change logs I think.

>  hw/i2c/Makefile.objs  |   1 +
>  hw/i2c/imx_i2c.c      | 352 ++++++++++++++++++++++++++++++++++++++++++++++++++
>  hw/i2c/imx_i2c_regs.h |  63 +++++++++
>  3 files changed, 416 insertions(+)
>  create mode 100644 hw/i2c/imx_i2c.c
>  create mode 100644 hw/i2c/imx_i2c_regs.h
>
> diff --git a/hw/i2c/Makefile.objs b/hw/i2c/Makefile.objs
> index 648278e..d27bbaa 100644
> --- a/hw/i2c/Makefile.objs
> +++ b/hw/i2c/Makefile.objs
> @@ -4,4 +4,5 @@ common-obj-$(CONFIG_ACPI) += smbus_ich9.o
>  common-obj-$(CONFIG_APM) += pm_smbus.o
>  common-obj-$(CONFIG_BITBANG_I2C) += bitbang_i2c.o
>  common-obj-$(CONFIG_EXYNOS4) += exynos4210_i2c.o
> +common-obj-$(CONFIG_IMX_I2C) += imx_i2c.o
>  obj-$(CONFIG_OMAP) += omap_i2c.o
> diff --git a/hw/i2c/imx_i2c.c b/hw/i2c/imx_i2c.c
> new file mode 100644
> index 0000000..9debcce
> --- /dev/null
> +++ b/hw/i2c/imx_i2c.c
> @@ -0,0 +1,352 @@
> +/*
> + *  i.MX I2C Bus Serial Interface Emulation
> + *
> + *  Copyright (C) 2013 Jean-Christophe Dubois.
> + *
> + *  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 "qemu/bitops.h"
> +
> +#include "hw/sysbus.h"
> +#include "hw/i2c/i2c.h"
> +
> +#include "hw/i2c/imx_i2c_regs.h"
> +
> +#ifndef IMX_I2C_DEBUG
> +#define IMX_I2C_DEBUG                 0
> +#endif
> +
> +#if IMX_I2C_DEBUG
> +#define DPRINT(fmt, ...)              \
> +    do { fprintf(stderr, "imx_i2c[%s]: " fmt, __func__, ## __VA_ARGS__); \
> +       } while (0)
> +
> +static const char *imx_i2c_get_regname(unsigned offset)
> +{
> +    switch (offset) {
> +    case IADR_ADDR:
> +        return "IADR";
> +    case IFDR_ADDR:
> +        return "IFDR";
> +    case I2CR_ADDR:
> +        return "I2CR";
> +    case I2SR_ADDR:
> +        return "I2SR";
> +    case I2DR_ADDR:
> +        return "I2DR";
> +    default:
> +        return "[?]";
> +    }
> +}
> +#else
> +#define DPRINT(fmt, args...)              do { } while (0)
> +#endif
> +
> +#define TYPE_IMX_I2C                  "imx.i2c"
> +#define IMX_I2C(obj)                  \
> +    OBJECT_CHECK(IMXI2CState, (obj), TYPE_IMX_I2C)
> +
> +typedef struct IMXI2CState {
> +    SysBusDevice parent_obj;
> +
> +    MemoryRegion iomem;
> +    i2c_bus *bus;
> +    qemu_irq irq;
> +
> +    uint16_t  address;
> +
> +    uint16_t iadr;
> +    uint16_t ifdr;
> +    uint16_t i2cr;
> +    uint16_t i2sr;
> +    uint16_t i2dr_read;
> +    uint16_t i2dr_write;
> +} IMXI2CState;
> +
> +static inline bool imx_i2c_is_enabled(IMXI2CState *s)
> +{
> +    return s->i2cr & I2CR_IEN;
> +}
> +
> +static inline bool imx_i2c_interrupt_is_enabled(IMXI2CState *s)
> +{
> +    return s->i2cr & I2CR_IIEN;
> +}
> +
> +static inline bool imx_i2c_is_master(IMXI2CState *s)
> +{
> +    return s->i2cr & I2CR_MSTA;
> +}
> +
> +static inline bool imx_i2c_direction_is_tx(IMXI2CState *s)
> +{
> +    return s->i2cr & I2CR_MTX;
> +}
> +
> +static void imx_i2c_reset(DeviceState *dev)
> +{
> +    IMXI2CState *s = IMX_I2C(dev);
> +
> +    if (s->address != ADDR_RESET) {
> +        i2c_end_transfer(s->bus);
> +    }

I don't think this is right, unless your device has actual logic that
cleans up the I2C bus on hard reset (which would be very strange). The
I2C bus should be responsible for its own reset as a reset (if needed
at all). As an I2C bus is stateless, cleanup of an inflight
transaction should happen naturally when a reset happens both master
and slave side. Did this manifest for you as a bug at any stage? If so
I think its a bug in the I2C framework and worth RFCing.

> +
> +    s->address    = ADDR_RESET;
> +    s->iadr       = IADR_RESET;
> +    s->ifdr       = IFDR_RESET;
> +    s->i2cr       = I2CR_RESET;
> +    s->i2sr       = I2SR_RESET;
> +    s->i2dr_read  = I2DR_RESET;
> +    s->i2dr_write = I2DR_RESET;
> +}
> +
> +static inline void imx_i2c_raise_interrupt(IMXI2CState *s)
> +{
> +    /*
> +     * raise an interrupt if the device is enabled and it is configured
> +     * to generate some interrupts.
> +     */
> +    if (imx_i2c_is_enabled(s) && imx_i2c_interrupt_is_enabled(s)) {
> +        s->i2sr |= I2SR_IIF;
> +        qemu_irq_raise(s->irq);
> +    }
> +}
> +
> +static uint64_t imx_i2c_read(void *opaque, hwaddr offset,
> +                             unsigned size)
> +{
> +    uint16_t value;
> +    IMXI2CState *s = IMX_I2C(opaque);
> +
> +    switch (offset) {
> +    case IADR_ADDR:
> +        value = s->iadr;
> +        break;
> +    case IFDR_ADDR:
> +        value = s->ifdr;
> +        break;
> +    case I2CR_ADDR:
> +        value = s->i2cr;
> +        break;
> +    case I2SR_ADDR:
> +        value = s->i2sr;
> +        break;
> +    case I2DR_ADDR:
> +        value = s->i2dr_read;
> +
> +        if (imx_i2c_is_master(s)) { /* master mode */
> +            int ret = 0xff;
> +
> +            if (s->address == ADDR_RESET) {
> +                /* something is wrong as the address is not set */
> +                DPRINT("Trying to read without specifying the slave address\n");

This DPRINT and the one below it looks guest accessible through SW
error and should be qemu_log_mask(LOG_GUEST_ERROR. Printfery should
generally be classified into three buckets.
qemu_log_mask(LOG_GUEST_ERROR, for the ones where bad software can
send you down obviously bad code paths like this. LOG_UNIMP for stuff
that we didn't bother with in QEMU. Lastly DPRINT (or just plain
qemu_log) is for extra verbosity that the debugging QEMU developer
needs to develop the device model.

> +            } else if (s->i2cr & I2CR_MTX) {
> +                DPRINT("Trying to read but MTX is set\n");
> +            } else {
> +                /* get the next byte */
> +                ret = i2c_recv(s->bus);
> +
> +                if (ret >= 0) {
> +                    imx_i2c_raise_interrupt(s);
> +                } else {
> +                    DPRINT("read failed for device 0x%02x\n" s->address);
> +                    ret = 0xff;
> +                }
> +            }
> +
> +            s->i2dr_read = ret;
> +        }
> +        break;
> +    default:
> +        hw_error("%s: Bad address 0x%x\n", __func__, (int)offset);
> +        break;
> +    }
> +
> +    DPRINT("read %s [0x%02x] -> 0x%02x\n", imx_i2c_get_regname(offset),
> +           offset, value);
> +
> +    return (uint64_t)value;
> +}
> +
> +static void imx_i2c_write(void *opaque, hwaddr offset,
> +                          uint64_t value, unsigned size)
> +{
> +    IMXI2CState *s = IMX_I2C(opaque);
> +
> +    DPRINT("write %s [0x%02x] <- 0x%02x\n", imx_i2c_get_regname(offset),
> +           offset, (int)value);
> +
> +    value &= 0xff;
> +
> +    switch (offset) {
> +    case IADR_ADDR:
> +        s->iadr = value & IADR_MASK;
> +        /* i2c_set_slave_address(s->bus, (uint8_t)s->iadr); */
> +        break;
> +    case IFDR_ADDR:
> +        s->ifdr = value & IFDR_MASK;
> +        break;
> +    case I2CR_ADDR:
> +        if (imx_i2c_is_enabled(s) && ((value & I2CR_IEN) == 0)) {
> +            /* This is a soft reset. IADR is preserved during soft resets */
> +            uint16_t iadr = s->iadr;
> +            imx_i2c_reset(&s->parent_obj.qdev);

QOM cast needed. And I think this may be in other parts of the series.
Replace all derefs of .qdev with DEVICE() casts.

imx_i2c_reset(DEVICE(s));

Regards,
Peter

> +            s->iadr = iadr;
> +        } else { /* normal write */
> +            s->i2cr = value & I2CR_MASK;
> +
> +            if (imx_i2c_is_master(s)) { /* master mode */
> +                /* set the bus to busy */
> +                s->i2sr |= I2SR_IBB;
> +            } else { /* slave mode */
> +                /* bus is not busy anymore */
> +                s->i2sr &= ~I2SR_IBB;
> +
> +                /*
> +                 *if we unset the master mode then it ends the ongoing
> +                 * transfer if any
> +                 */
> +                if (s->address != ADDR_RESET) {
> +                    i2c_end_transfer(s->bus);
> +                    s->address = ADDR_RESET;
> +                }
> +            }
> +
> +            if (s->i2cr & I2CR_RSTA) { /* Restart */
> +                /* if this is a restart then it ends the ongoing transfer */
> +                if (s->address != ADDR_RESET) {
> +                    i2c_end_transfer(s->bus);
> +                    s->address = ADDR_RESET;
> +                    s->i2cr &= ~I2CR_RSTA;
> +                }
> +            }
> +        }
> +        break;
> +    case I2SR_ADDR:
> +        /*
> +         * if the user writes 0 to IIF then lower the interrupt and
> +         * reset the bit
> +         */
> +        if ((s->i2sr & I2SR_IIF) && !(value & I2SR_IIF)) {
> +            s->i2sr &= ~I2SR_IIF;
> +            qemu_irq_lower(s->irq);
> +        }
> +
> +        /*
> +         * if the user writes 0 to IAL, reset the bit
> +         */
> +        if ((s->i2sr & I2SR_IAL) && !(value & I2SR_IAL)) {
> +            s->i2sr &= ~I2SR_IAL;
> +        }
> +
> +        break;
> +    case I2DR_ADDR:
> +        /* if the device is not enabled, nothing to do */
> +        if (!imx_i2c_is_enabled(s)) {
> +            break;
> +        }
> +
> +        s->i2dr_write = value & I2DR_MASK;
> +
> +        if (imx_i2c_is_master(s)) { /* master mode */
> +            /* If this is the first write cycle then it is the slave addr */
> +            if (s->address == ADDR_RESET) {
> +                if (i2c_start_transfer(s->bus, extract32(s->i2dr_write, 1, 7),
> +                                       extract32(s->i2dr_write, 0, 1))) {
> +                    /* if non zero is returned, the adress is not valid */
> +                    s->i2sr |= I2SR_RXAK;
> +                } else {
> +                    s->address = s->i2dr_write;
> +                    s->i2sr &= ~I2SR_RXAK;
> +                    imx_i2c_raise_interrupt(s);
> +                }
> +            } else { /* This is a normal data write */
> +                if (i2c_send(s->bus, s->i2dr_write)) {
> +                    /* if the target return non zero then end the transfer */
> +                    s->i2sr |= I2SR_RXAK;
> +                    s->address = ADDR_RESET;
> +                    i2c_end_transfer(s->bus);
> +                } else {
> +                    s->i2sr &= ~I2SR_RXAK;
> +                    imx_i2c_raise_interrupt(s);
> +                }
> +            }
> +        }
> +        break;
> +    default:
> +        hw_error("%s: Bad address 0x%x\n", __func__, (int)offset);
> +        break;
> +    }
> +}
> +
> +static const MemoryRegionOps imx_i2c_ops = {
> +    .read = imx_i2c_read,
> +    .write = imx_i2c_write,
> +    .valid.min_access_size = 1,
> +    .valid.max_access_size = 2,
> +    .endianness = DEVICE_NATIVE_ENDIAN,
> +};
> +
> +static const VMStateDescription imx_i2c_vmstate = {
> +    .name = TYPE_IMX_I2C,
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_UINT16(address, IMXI2CState),
> +        VMSTATE_UINT16(iadr, IMXI2CState),
> +        VMSTATE_UINT16(ifdr, IMXI2CState),
> +        VMSTATE_UINT16(i2cr, IMXI2CState),
> +        VMSTATE_UINT16(i2sr, IMXI2CState),
> +        VMSTATE_UINT16(i2dr_read, IMXI2CState),
> +        VMSTATE_UINT16(i2dr_write, IMXI2CState),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static void imx_i2c_realize(DeviceState *dev, Error **errp)
> +{
> +    IMXI2CState *s = IMX_I2C(dev);
> +    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
> +
> +    memory_region_init_io(&s->iomem, &imx_i2c_ops, s, TYPE_IMX_I2C,
> +                          IMX_I2C_MEM_SIZE);
> +    sysbus_init_mmio(sbd, &s->iomem);
> +    sysbus_init_irq(sbd, &s->irq);
> +    s->bus = i2c_init_bus(DEVICE(dev), "i2c");
> +}
> +
> +static void imx_i2c_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    dc->vmsd = &imx_i2c_vmstate;
> +    dc->reset = imx_i2c_reset;
> +    dc->realize = imx_i2c_realize;
> +}
> +
> +static const TypeInfo imx_i2c_type_info = {
> +    .name = TYPE_IMX_I2C,
> +    .parent = TYPE_SYS_BUS_DEVICE,
> +    .instance_size = sizeof(IMXI2CState),
> +    .class_init = imx_i2c_class_init,
> +};
> +
> +static void imx_i2c_register_types(void)
> +{
> +    type_register_static(&imx_i2c_type_info);
> +}
> +
> +type_init(imx_i2c_register_types)
> diff --git a/hw/i2c/imx_i2c_regs.h b/hw/i2c/imx_i2c_regs.h
> new file mode 100644
> index 0000000..95c7cf5
> --- /dev/null
> +++ b/hw/i2c/imx_i2c_regs.h
> @@ -0,0 +1,63 @@
> +/*
> + *  i.MX I2C Bus Serial Interface registers definition
> + *
> + *  Copyright (C) 2013 Jean-Christophe Dubois.
> + *
> + *  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/>.
> + *
> + */
> +
> +#ifndef __IMX_I2C_REGS_H_
> +#define __IMX_I2C_REGS_H_
> +
> +#define IMX_I2C_MEM_SIZE           0x14
> +
> +/* i.MX I2C memory map */
> +#define IADR_ADDR                  0x00  /* address register */
> +#define IFDR_ADDR                  0x04  /* frequency divider register */
> +#define I2CR_ADDR                  0x08  /* control register */
> +#define I2SR_ADDR                  0x0c  /* status register */
> +#define I2DR_ADDR                  0x10  /* data register */
> +
> +#define IADR_MASK                  0xFE
> +#define IADR_RESET                 0
> +
> +#define IFDR_MASK                  0x3F
> +#define IFDR_RESET                 0
> +
> +#define I2CR_IEN                   (1 << 7)
> +#define I2CR_IIEN                  (1 << 6)
> +#define I2CR_MSTA                  (1 << 5)
> +#define I2CR_MTX                   (1 << 4)
> +#define I2CR_TXAK                  (1 << 3)
> +#define I2CR_RSTA                  (1 << 2)
> +#define I2CR_MASK                  0xFC
> +#define I2CR_RESET                 0
> +
> +#define I2SR_ICF                   (1 << 7)
> +#define I2SR_IAAF                  (1 << 6)
> +#define I2SR_IBB                   (1 << 5)
> +#define I2SR_IAL                   (1 << 4)
> +#define I2SR_SRW                   (1 << 2)
> +#define I2SR_IIF                   (1 << 1)
> +#define I2SR_RXAK                  (1 << 0)
> +#define I2SR_MASK                  0xE9
> +#define I2SR_RESET                 0x81
> +
> +#define I2DR_MASK                  0xFF
> +#define I2DR_RESET                 0
> +
> +#define ADDR_RESET                 0xFF00
> +
> +#endif /* __IMX_I2C_REGS_H_ */
> --
> 1.8.1.2
>
>

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [Qemu-devel] [PATCH v3 2/4] Add i.MX I2C controller emulator
  2013-05-06  2:19   ` Peter Crosthwaite
@ 2013-05-06  6:26     ` Jean-Christophe DUBOIS
  0 siblings, 0 replies; 7+ messages in thread
From: Jean-Christophe DUBOIS @ 2013-05-06  6:26 UTC (permalink / raw)
  To: Peter Crosthwaite; +Cc: peter.maydell, peter.chubb, qemu-devel, afaerber

On 05/06/2013 04:19 AM, Peter Crosthwaite wrote:
> Hi JC,
>
> On Mon, May 6, 2013 at 12:28 AM, Jean-Christophe DUBOIS
> <jcd@tribudubois.net> wrote:
>> The slave mode is not implemented.
>>
>> Changes since v1:
>>      * use QOM cast
>>      * run checkpatch on code
>>      * added restrictin on MemoryRegionOps
>>      * use DeviceClass::realise as init function
>>
>> Changes since v2:
>>      * use CamelCase for state type
>>      * use extrac32() for bit manipilation.
>>      * improve QOM cast
>>      * separate regs definition in its own file (to be reused by qtest)
>>
> Per-patch change logs need to go below the line ...
>
>> Signed-off-by: Jean-Christophe DUBOIS <jcd@tribudubois.net>
>> ---
> ... here. Otherwise it will go into the mainline git log on merge.
>
> The edit can be done by hand edit before the send-email. But I use a
> scripted approach personally to move my changelogs out of commit
> message pre send (and my working tree has commits formatted similar to
> what you have sent here). Others using the manual flow tend to just
> use cover letter change logs I think.
OK
>
>>   hw/i2c/Makefile.objs  |   1 +
>>   hw/i2c/imx_i2c.c      | 352 ++++++++++++++++++++++++++++++++++++++++++++++++++
>>   hw/i2c/imx_i2c_regs.h |  63 +++++++++
>>   3 files changed, 416 insertions(+)
>>   create mode 100644 hw/i2c/imx_i2c.c
>>   create mode 100644 hw/i2c/imx_i2c_regs.h
>>
>> diff --git a/hw/i2c/Makefile.objs b/hw/i2c/Makefile.objs
>> index 648278e..d27bbaa 100644
>> --- a/hw/i2c/Makefile.objs
>> +++ b/hw/i2c/Makefile.objs
>> @@ -4,4 +4,5 @@ common-obj-$(CONFIG_ACPI) += smbus_ich9.o
>>   common-obj-$(CONFIG_APM) += pm_smbus.o
>>   common-obj-$(CONFIG_BITBANG_I2C) += bitbang_i2c.o
>>   common-obj-$(CONFIG_EXYNOS4) += exynos4210_i2c.o
>> +common-obj-$(CONFIG_IMX_I2C) += imx_i2c.o
>>   obj-$(CONFIG_OMAP) += omap_i2c.o
>> diff --git a/hw/i2c/imx_i2c.c b/hw/i2c/imx_i2c.c
>> new file mode 100644
>> index 0000000..9debcce
>> --- /dev/null
>> +++ b/hw/i2c/imx_i2c.c
>> @@ -0,0 +1,352 @@
>> +/*
>> + *  i.MX I2C Bus Serial Interface Emulation
>> + *
>> + *  Copyright (C) 2013 Jean-Christophe Dubois.
>> + *
>> + *  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 "qemu/bitops.h"
>> +
>> +#include "hw/sysbus.h"
>> +#include "hw/i2c/i2c.h"
>> +
>> +#include "hw/i2c/imx_i2c_regs.h"
>> +
>> +#ifndef IMX_I2C_DEBUG
>> +#define IMX_I2C_DEBUG                 0
>> +#endif
>> +
>> +#if IMX_I2C_DEBUG
>> +#define DPRINT(fmt, ...)              \
>> +    do { fprintf(stderr, "imx_i2c[%s]: " fmt, __func__, ## __VA_ARGS__); \
>> +       } while (0)
>> +
>> +static const char *imx_i2c_get_regname(unsigned offset)
>> +{
>> +    switch (offset) {
>> +    case IADR_ADDR:
>> +        return "IADR";
>> +    case IFDR_ADDR:
>> +        return "IFDR";
>> +    case I2CR_ADDR:
>> +        return "I2CR";
>> +    case I2SR_ADDR:
>> +        return "I2SR";
>> +    case I2DR_ADDR:
>> +        return "I2DR";
>> +    default:
>> +        return "[?]";
>> +    }
>> +}
>> +#else
>> +#define DPRINT(fmt, args...)              do { } while (0)
>> +#endif
>> +
>> +#define TYPE_IMX_I2C                  "imx.i2c"
>> +#define IMX_I2C(obj)                  \
>> +    OBJECT_CHECK(IMXI2CState, (obj), TYPE_IMX_I2C)
>> +
>> +typedef struct IMXI2CState {
>> +    SysBusDevice parent_obj;
>> +
>> +    MemoryRegion iomem;
>> +    i2c_bus *bus;
>> +    qemu_irq irq;
>> +
>> +    uint16_t  address;
>> +
>> +    uint16_t iadr;
>> +    uint16_t ifdr;
>> +    uint16_t i2cr;
>> +    uint16_t i2sr;
>> +    uint16_t i2dr_read;
>> +    uint16_t i2dr_write;
>> +} IMXI2CState;
>> +
>> +static inline bool imx_i2c_is_enabled(IMXI2CState *s)
>> +{
>> +    return s->i2cr & I2CR_IEN;
>> +}
>> +
>> +static inline bool imx_i2c_interrupt_is_enabled(IMXI2CState *s)
>> +{
>> +    return s->i2cr & I2CR_IIEN;
>> +}
>> +
>> +static inline bool imx_i2c_is_master(IMXI2CState *s)
>> +{
>> +    return s->i2cr & I2CR_MSTA;
>> +}
>> +
>> +static inline bool imx_i2c_direction_is_tx(IMXI2CState *s)
>> +{
>> +    return s->i2cr & I2CR_MTX;
>> +}
>> +
>> +static void imx_i2c_reset(DeviceState *dev)
>> +{
>> +    IMXI2CState *s = IMX_I2C(dev);
>> +
>> +    if (s->address != ADDR_RESET) {
>> +        i2c_end_transfer(s->bus);
>> +    }
> I don't think this is right, unless your device has actual logic that
> cleans up the I2C bus on hard reset (which would be very strange). The
> I2C bus should be responsible for its own reset as a reset (if needed
> at all). As an I2C bus is stateless, cleanup of an inflight
> transaction should happen naturally when a reset happens both master
> and slave side. Did this manifest for you as a bug at any stage? If so
> I think its a bug in the I2C framework and worth RFCing.

No, I was trying to be extra cautious and leave a clean state. I'll 
remove the i2c_end_transfer().
>> +
>> +    s->address    = ADDR_RESET;
>> +    s->iadr       = IADR_RESET;
>> +    s->ifdr       = IFDR_RESET;
>> +    s->i2cr       = I2CR_RESET;
>> +    s->i2sr       = I2SR_RESET;
>> +    s->i2dr_read  = I2DR_RESET;
>> +    s->i2dr_write = I2DR_RESET;
>> +}
>> +
>> +static inline void imx_i2c_raise_interrupt(IMXI2CState *s)
>> +{
>> +    /*
>> +     * raise an interrupt if the device is enabled and it is configured
>> +     * to generate some interrupts.
>> +     */
>> +    if (imx_i2c_is_enabled(s) && imx_i2c_interrupt_is_enabled(s)) {
>> +        s->i2sr |= I2SR_IIF;
>> +        qemu_irq_raise(s->irq);
>> +    }
>> +}
>> +
>> +static uint64_t imx_i2c_read(void *opaque, hwaddr offset,
>> +                             unsigned size)
>> +{
>> +    uint16_t value;
>> +    IMXI2CState *s = IMX_I2C(opaque);
>> +
>> +    switch (offset) {
>> +    case IADR_ADDR:
>> +        value = s->iadr;
>> +        break;
>> +    case IFDR_ADDR:
>> +        value = s->ifdr;
>> +        break;
>> +    case I2CR_ADDR:
>> +        value = s->i2cr;
>> +        break;
>> +    case I2SR_ADDR:
>> +        value = s->i2sr;
>> +        break;
>> +    case I2DR_ADDR:
>> +        value = s->i2dr_read;
>> +
>> +        if (imx_i2c_is_master(s)) { /* master mode */
>> +            int ret = 0xff;
>> +
>> +            if (s->address == ADDR_RESET) {
>> +                /* something is wrong as the address is not set */
>> +                DPRINT("Trying to read without specifying the slave address\n");
> This DPRINT and the one below it looks guest accessible through SW
> error and should be qemu_log_mask(LOG_GUEST_ERROR. Printfery should
> generally be classified into three buckets.
> qemu_log_mask(LOG_GUEST_ERROR, for the ones where bad software can
> send you down obviously bad code paths like this. LOG_UNIMP for stuff
> that we didn't bother with in QEMU. Lastly DPRINT (or just plain
> qemu_log) is for extra verbosity that the debugging QEMU developer
> needs to develop the device model.

OK
>
>> +            } else if (s->i2cr & I2CR_MTX) {
>> +                DPRINT("Trying to read but MTX is set\n");
>> +            } else {
>> +                /* get the next byte */
>> +                ret = i2c_recv(s->bus);
>> +
>> +                if (ret >= 0) {
>> +                    imx_i2c_raise_interrupt(s);
>> +                } else {
>> +                    DPRINT("read failed for device 0x%02x\n" s->address);
>> +                    ret = 0xff;
>> +                }
>> +            }
>> +
>> +            s->i2dr_read = ret;
>> +        }
>> +        break;
>> +    default:
>> +        hw_error("%s: Bad address 0x%x\n", __func__, (int)offset);
>> +        break;
>> +    }
>> +
>> +    DPRINT("read %s [0x%02x] -> 0x%02x\n", imx_i2c_get_regname(offset),
>> +           offset, value);
>> +
>> +    return (uint64_t)value;
>> +}
>> +
>> +static void imx_i2c_write(void *opaque, hwaddr offset,
>> +                          uint64_t value, unsigned size)
>> +{
>> +    IMXI2CState *s = IMX_I2C(opaque);
>> +
>> +    DPRINT("write %s [0x%02x] <- 0x%02x\n", imx_i2c_get_regname(offset),
>> +           offset, (int)value);
>> +
>> +    value &= 0xff;
>> +
>> +    switch (offset) {
>> +    case IADR_ADDR:
>> +        s->iadr = value & IADR_MASK;
>> +        /* i2c_set_slave_address(s->bus, (uint8_t)s->iadr); */
>> +        break;
>> +    case IFDR_ADDR:
>> +        s->ifdr = value & IFDR_MASK;
>> +        break;
>> +    case I2CR_ADDR:
>> +        if (imx_i2c_is_enabled(s) && ((value & I2CR_IEN) == 0)) {
>> +            /* This is a soft reset. IADR is preserved during soft resets */
>> +            uint16_t iadr = s->iadr;
>> +            imx_i2c_reset(&s->parent_obj.qdev);
> QOM cast needed. And I think this may be in other parts of the series.
> Replace all derefs of .qdev with DEVICE() casts.
>
> imx_i2c_reset(DEVICE(s));
OK
>
> Regards,
> Peter
>
>> +            s->iadr = iadr;
>> +        } else { /* normal write */
>> +            s->i2cr = value & I2CR_MASK;
>> +
>> +            if (imx_i2c_is_master(s)) { /* master mode */
>> +                /* set the bus to busy */
>> +                s->i2sr |= I2SR_IBB;
>> +            } else { /* slave mode */
>> +                /* bus is not busy anymore */
>> +                s->i2sr &= ~I2SR_IBB;
>> +
>> +                /*
>> +                 *if we unset the master mode then it ends the ongoing
>> +                 * transfer if any
>> +                 */
>> +                if (s->address != ADDR_RESET) {
>> +                    i2c_end_transfer(s->bus);
>> +                    s->address = ADDR_RESET;
>> +                }
>> +            }
>> +
>> +            if (s->i2cr & I2CR_RSTA) { /* Restart */
>> +                /* if this is a restart then it ends the ongoing transfer */
>> +                if (s->address != ADDR_RESET) {
>> +                    i2c_end_transfer(s->bus);
>> +                    s->address = ADDR_RESET;
>> +                    s->i2cr &= ~I2CR_RSTA;
>> +                }
>> +            }
>> +        }
>> +        break;
>> +    case I2SR_ADDR:
>> +        /*
>> +         * if the user writes 0 to IIF then lower the interrupt and
>> +         * reset the bit
>> +         */
>> +        if ((s->i2sr & I2SR_IIF) && !(value & I2SR_IIF)) {
>> +            s->i2sr &= ~I2SR_IIF;
>> +            qemu_irq_lower(s->irq);
>> +        }
>> +
>> +        /*
>> +         * if the user writes 0 to IAL, reset the bit
>> +         */
>> +        if ((s->i2sr & I2SR_IAL) && !(value & I2SR_IAL)) {
>> +            s->i2sr &= ~I2SR_IAL;
>> +        }
>> +
>> +        break;
>> +    case I2DR_ADDR:
>> +        /* if the device is not enabled, nothing to do */
>> +        if (!imx_i2c_is_enabled(s)) {
>> +            break;
>> +        }
>> +
>> +        s->i2dr_write = value & I2DR_MASK;
>> +
>> +        if (imx_i2c_is_master(s)) { /* master mode */
>> +            /* If this is the first write cycle then it is the slave addr */
>> +            if (s->address == ADDR_RESET) {
>> +                if (i2c_start_transfer(s->bus, extract32(s->i2dr_write, 1, 7),
>> +                                       extract32(s->i2dr_write, 0, 1))) {
>> +                    /* if non zero is returned, the adress is not valid */
>> +                    s->i2sr |= I2SR_RXAK;
>> +                } else {
>> +                    s->address = s->i2dr_write;
>> +                    s->i2sr &= ~I2SR_RXAK;
>> +                    imx_i2c_raise_interrupt(s);
>> +                }
>> +            } else { /* This is a normal data write */
>> +                if (i2c_send(s->bus, s->i2dr_write)) {
>> +                    /* if the target return non zero then end the transfer */
>> +                    s->i2sr |= I2SR_RXAK;
>> +                    s->address = ADDR_RESET;
>> +                    i2c_end_transfer(s->bus);
>> +                } else {
>> +                    s->i2sr &= ~I2SR_RXAK;
>> +                    imx_i2c_raise_interrupt(s);
>> +                }
>> +            }
>> +        }
>> +        break;
>> +    default:
>> +        hw_error("%s: Bad address 0x%x\n", __func__, (int)offset);
>> +        break;
>> +    }
>> +}
>> +
>> +static const MemoryRegionOps imx_i2c_ops = {
>> +    .read = imx_i2c_read,
>> +    .write = imx_i2c_write,
>> +    .valid.min_access_size = 1,
>> +    .valid.max_access_size = 2,
>> +    .endianness = DEVICE_NATIVE_ENDIAN,
>> +};
>> +
>> +static const VMStateDescription imx_i2c_vmstate = {
>> +    .name = TYPE_IMX_I2C,
>> +    .version_id = 1,
>> +    .minimum_version_id = 1,
>> +    .fields = (VMStateField[]) {
>> +        VMSTATE_UINT16(address, IMXI2CState),
>> +        VMSTATE_UINT16(iadr, IMXI2CState),
>> +        VMSTATE_UINT16(ifdr, IMXI2CState),
>> +        VMSTATE_UINT16(i2cr, IMXI2CState),
>> +        VMSTATE_UINT16(i2sr, IMXI2CState),
>> +        VMSTATE_UINT16(i2dr_read, IMXI2CState),
>> +        VMSTATE_UINT16(i2dr_write, IMXI2CState),
>> +        VMSTATE_END_OF_LIST()
>> +    }
>> +};
>> +
>> +static void imx_i2c_realize(DeviceState *dev, Error **errp)
>> +{
>> +    IMXI2CState *s = IMX_I2C(dev);
>> +    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
>> +
>> +    memory_region_init_io(&s->iomem, &imx_i2c_ops, s, TYPE_IMX_I2C,
>> +                          IMX_I2C_MEM_SIZE);
>> +    sysbus_init_mmio(sbd, &s->iomem);
>> +    sysbus_init_irq(sbd, &s->irq);
>> +    s->bus = i2c_init_bus(DEVICE(dev), "i2c");
>> +}
>> +
>> +static void imx_i2c_class_init(ObjectClass *klass, void *data)
>> +{
>> +    DeviceClass *dc = DEVICE_CLASS(klass);
>> +
>> +    dc->vmsd = &imx_i2c_vmstate;
>> +    dc->reset = imx_i2c_reset;
>> +    dc->realize = imx_i2c_realize;
>> +}
>> +
>> +static const TypeInfo imx_i2c_type_info = {
>> +    .name = TYPE_IMX_I2C,
>> +    .parent = TYPE_SYS_BUS_DEVICE,
>> +    .instance_size = sizeof(IMXI2CState),
>> +    .class_init = imx_i2c_class_init,
>> +};
>> +
>> +static void imx_i2c_register_types(void)
>> +{
>> +    type_register_static(&imx_i2c_type_info);
>> +}
>> +
>> +type_init(imx_i2c_register_types)
>> diff --git a/hw/i2c/imx_i2c_regs.h b/hw/i2c/imx_i2c_regs.h
>> new file mode 100644
>> index 0000000..95c7cf5
>> --- /dev/null
>> +++ b/hw/i2c/imx_i2c_regs.h
>> @@ -0,0 +1,63 @@
>> +/*
>> + *  i.MX I2C Bus Serial Interface registers definition
>> + *
>> + *  Copyright (C) 2013 Jean-Christophe Dubois.
>> + *
>> + *  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/>.
>> + *
>> + */
>> +
>> +#ifndef __IMX_I2C_REGS_H_
>> +#define __IMX_I2C_REGS_H_
>> +
>> +#define IMX_I2C_MEM_SIZE           0x14
>> +
>> +/* i.MX I2C memory map */
>> +#define IADR_ADDR                  0x00  /* address register */
>> +#define IFDR_ADDR                  0x04  /* frequency divider register */
>> +#define I2CR_ADDR                  0x08  /* control register */
>> +#define I2SR_ADDR                  0x0c  /* status register */
>> +#define I2DR_ADDR                  0x10  /* data register */
>> +
>> +#define IADR_MASK                  0xFE
>> +#define IADR_RESET                 0
>> +
>> +#define IFDR_MASK                  0x3F
>> +#define IFDR_RESET                 0
>> +
>> +#define I2CR_IEN                   (1 << 7)
>> +#define I2CR_IIEN                  (1 << 6)
>> +#define I2CR_MSTA                  (1 << 5)
>> +#define I2CR_MTX                   (1 << 4)
>> +#define I2CR_TXAK                  (1 << 3)
>> +#define I2CR_RSTA                  (1 << 2)
>> +#define I2CR_MASK                  0xFC
>> +#define I2CR_RESET                 0
>> +
>> +#define I2SR_ICF                   (1 << 7)
>> +#define I2SR_IAAF                  (1 << 6)
>> +#define I2SR_IBB                   (1 << 5)
>> +#define I2SR_IAL                   (1 << 4)
>> +#define I2SR_SRW                   (1 << 2)
>> +#define I2SR_IIF                   (1 << 1)
>> +#define I2SR_RXAK                  (1 << 0)
>> +#define I2SR_MASK                  0xE9
>> +#define I2SR_RESET                 0x81
>> +
>> +#define I2DR_MASK                  0xFF
>> +#define I2DR_RESET                 0
>> +
>> +#define ADDR_RESET                 0xFF00
>> +
>> +#endif /* __IMX_I2C_REGS_H_ */
>> --
>> 1.8.1.2
>>
>>

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2013-05-06  6:26 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2013-05-05 14:28 [Qemu-devel] [PATCH v3 0/4] Add i.MX25 support through the 3DS evaluation board Jean-Christophe DUBOIS
2013-05-05 14:28 ` [Qemu-devel] [PATCH v3 1/4] Add i.MX FEC Ethernet emulator Jean-Christophe DUBOIS
2013-05-05 14:28 ` [Qemu-devel] [PATCH v3 2/4] Add i.MX I2C controller emulator Jean-Christophe DUBOIS
2013-05-06  2:19   ` Peter Crosthwaite
2013-05-06  6:26     ` Jean-Christophe DUBOIS
2013-05-05 14:29 ` [Qemu-devel] [PATCH v3 3/4] Add i.MX25 3DS evaluation board support Jean-Christophe DUBOIS
2013-05-05 14:29 ` [Qemu-devel] [PATCH v3 4/4] Add qtest support for i.MX I2C device emulation Jean-Christophe DUBOIS

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).