Patchwork [10/18] hw: add QEMU model for Faraday MMC/SD host controller

login
register
mail settings
Submitter Dante
Date Jan. 18, 2013, 6:31 a.m.
Message ID <1358490683-18519-1-git-send-email-dantesu@faraday-tech.com>
Download mbox | patch
Permalink /patch/213492/
State New
Headers show

Comments

Dante - Jan. 18, 2013, 6:31 a.m.
Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
---
 hw/ftsdc010.c |  360 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/ftsdc010.h |  103 +++++++++++++++++
 2 files changed, 463 insertions(+)
 create mode 100644 hw/ftsdc010.c
 create mode 100644 hw/ftsdc010.h

Patch

diff --git a/hw/ftsdc010.c b/hw/ftsdc010.c
new file mode 100644
index 0000000..006fcae
--- /dev/null
+++ b/hw/ftsdc010.c
@@ -0,0 +1,360 @@ 
+/*
+ * QEMU model of the FTSDC010 MMC/SD Host Controller
+ *
+ * Copyright (C) 2012 Faraday Technology
+ * Copyright (C) 2012 Dante Su <dantesu@faraday-tech.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "sysbus.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/blockdev.h"
+#include "sd.h"
+
+#include "ftsdc010.h"
+
+typedef struct {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+    SDState *card;
+    qemu_irq irq;
+    
+    /* DMA hardware handshake */
+    qemu_irq req;
+    
+    uint32_t datacnt;
+    
+    /* HW register cache */
+    uint32_t cmd;
+    uint32_t arg;
+    uint32_t rsp[4];
+    uint32_t rspcmd;
+    uint32_t dcr;
+    uint32_t dtr;
+    uint32_t dlr;
+    uint32_t status;
+    uint32_t ier;
+    uint32_t pwr;
+    uint32_t clk;
+} ftsdc010_state;
+
+static inline void ftsdc010_update_irq(ftsdc010_state *s)
+{
+    if (s->ier & s->status)
+        qemu_set_irq(s->irq, 1);
+    else
+        qemu_set_irq(s->irq, 0);
+}
+
+static void ftsdc010_handle_ack(void *opaque, int line, int level)
+{
+    ftsdc010_state *s = (ftsdc010_state *)opaque;
+    
+    if (!(s->dcr & DCR_DMA))
+        return;
+    
+    if (level) {
+        qemu_set_irq(s->req, 0);
+    } else if (s->datacnt > 0) {
+        qemu_set_irq(s->req, 1);
+    }
+}
+
+static void ftsdc010_send_command(ftsdc010_state *s)
+{
+    SDRequest request;
+    uint8_t response[16];
+    int rlen;
+
+    request.cmd = s->cmd & CMD_IDX;
+    request.arg = s->arg;
+
+    rlen = sd_do_command(s->card, &request, response);
+    if (rlen < 0)
+        goto error;
+    if (s->cmd & CMD_WAIT_RSP) {
+#define RWORD(n) ((response[n] << 24) | (response[n + 1] << 16) \
+                  | (response[n + 2] << 8) | response[n + 3])
+        if (rlen == 0 || (rlen == 4 && (s->cmd & CMD_LONG_RSP)))
+            goto error;
+        if (rlen != 4 && rlen != 16)
+            goto error;
+        if (rlen == 4) {
+            s->rsp[0] = RWORD(0);
+            s->rsp[1] = s->rsp[2] = s->rsp[3] = 0;
+        } else {
+            s->rsp[3] = RWORD(0);
+            s->rsp[2] = RWORD(4);
+            s->rsp[1] = RWORD(8);
+            s->rsp[0] = RWORD(12) & ~1;
+        }
+        s->rspcmd  = (s->cmd & CMD_IDX);
+        s->rspcmd |= (s->cmd & 0x100) ? (1 << 6) : 0;
+        s->status |= STR_RSP;
+#undef RWORD
+    } else {
+        s->status |= STR_CMD;
+    }
+
+    if ((s->dcr & DCR_DMA) && (s->datacnt > 0))
+        qemu_set_irq(s->req, 1);
+
+    return;
+
+error:
+    s->status |= STR_RSP_TIMEOUT;
+}
+
+static void ftsdc010_chip_reset(ftsdc010_state *s)
+{
+    s->cmd = 0;
+    s->arg = 0;
+    s->rsp[0] = 0;
+    s->rsp[1] = 0;
+    s->rsp[2] = 0;
+    s->rsp[3] = 0;
+    s->rspcmd = 0;
+    s->dcr = 0;
+    s->dtr = 0;
+    s->dlr = 0;
+    s->datacnt = 0;
+    s->status &= ~(STR_CARD_REMOVED | STR_WPROT);
+    s->status |= STR_TXRDY | STR_RXRDY;
+    s->ier = 0;
+    s->pwr = 0;
+    s->clk = 0;    
+}
+
+static uint64_t ftsdc010_mem_read(void *opaque, hwaddr addr, unsigned int size)
+{
+    ftsdc010_state *s = opaque;
+    uint32_t ret = 0;
+
+    switch(addr) {
+    case REG_STR:
+        return s->status;
+    case REG_DWR:
+        if (!(s->dcr & DCR_WR) && (s->datacnt > 0)) {
+            ret = sd_read_data(s->card)
+                | sd_read_data(s->card) << 8
+                | sd_read_data(s->card) << 16
+                | sd_read_data(s->card) << 24;
+            s->datacnt -= 4;
+            if (s->datacnt <= 0)
+                s->status |= STR_DAT_END;
+            s->status |= STR_DAT;
+        }
+        break;
+    case REG_DCR:
+        return s->dcr;
+    case REG_DTR:
+        return s->dtr;
+    case REG_DLR:
+        return s->dlr;
+    case REG_CMD:
+        return s->cmd;
+    case REG_ARG:
+        return s->arg;
+    case REG_RSP0:
+        return s->rsp[0];
+    case REG_RSP1:
+        return s->rsp[1];
+    case REG_RSP2:
+        return s->rsp[2];
+    case REG_RSP3:
+        return s->rsp[3];
+    case REG_RSPCMD:
+        return s->rspcmd;
+    case REG_IER:
+        return s->ier;
+    case REG_PWR:
+        return s->pwr;
+    case REG_CLK:
+        return s->clk;
+    case REG_BUS:
+        return 0x00000009;
+    case REG_FEA:
+        return 0x00000010;
+    case REG_REV:
+        return 0x00030300;
+    default:
+        break;
+    }
+
+    return ret;
+}
+
+static void ftsdc010_mem_write(void *opaque, hwaddr addr, uint64_t val, unsigned int size)
+{
+    ftsdc010_state *s = opaque;
+    
+    switch(addr) {
+    case REG_DWR:
+        if ((s->dcr & DCR_WR) && (s->datacnt > 0)) {
+            sd_write_data(s->card, ((uint32_t)val >>  0) & 0xff);
+            sd_write_data(s->card, ((uint32_t)val >>  8) & 0xff);
+            sd_write_data(s->card, ((uint32_t)val >> 16) & 0xff);
+            sd_write_data(s->card, ((uint32_t)val >> 24) & 0xff);
+            s->datacnt -= 4;
+            if (s->datacnt <= 0)
+                s->status |= STR_DAT_END;
+            s->status |= STR_DAT;
+        }
+        break;
+    case REG_CMD:
+        s->cmd = (uint32_t)val;
+        if (s->cmd & (1 << 10)) {
+            ftsdc010_chip_reset(s);
+        } else if (s->cmd & (1 << 9)) {
+            s->cmd &= ~(1 << 9);
+            s->status &= ~(STR_CMD | STR_RSP | STR_RSP_ERR);
+            ftsdc010_send_command(s);
+        }
+        break;
+    case REG_ARG:
+        s->arg = (uint32_t)val;
+        break;
+    case REG_DCR:
+        s->dcr = (uint32_t)val;
+        if (s->dcr & DCR_EN) {
+            s->dcr &= ~(DCR_EN);
+            s->status &= ~(STR_DAT | STR_DAT_END | STR_DAT_ERR);
+            s->datacnt = s->dlr;
+        }
+        break;
+    case REG_DTR:
+        s->dtr = (uint32_t)val;
+        break;
+    case REG_DLR:
+        s->dlr = (uint32_t)val;
+        break;
+    case REG_SCR:
+        s->status &= ~((uint32_t)val & 0x14ff);
+        ftsdc010_update_irq(s);
+        break;
+    case REG_IER:
+        s->ier = (uint32_t)val;
+        ftsdc010_update_irq(s);
+        break;
+    case REG_PWR:
+        s->pwr = (uint32_t)val;
+        break;
+    case REG_CLK:
+        s->clk = (uint32_t)val;
+        break;
+    default:
+        break;
+    }
+}
+
+static const MemoryRegionOps ftsdc010_mem_ops = {
+    .read  = ftsdc010_mem_read,
+    .write = ftsdc010_mem_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4
+    }
+};
+
+static void ftsdc010_reset(DeviceState *d)
+{
+    ftsdc010_state *s = DO_UPCAST(ftsdc010_state, busdev.qdev, d);
+    
+    ftsdc010_chip_reset(s);
+
+    sd_set_cb(s->card, NULL, NULL);
+    
+    /* We can assume our GPIO outputs have been wired up now */
+    qemu_set_irq(s->req, 0);
+}
+
+static int ftsdc010_init(SysBusDevice *dev)
+{
+    ftsdc010_state *s = FROM_SYSBUS(typeof(*s), dev);
+    DriveInfo *dinfo;
+
+    memory_region_init_io(&s->iomem, &ftsdc010_mem_ops, s, "ftsdc010", 0x1000);
+    sysbus_init_mmio(dev, &s->iomem);
+    sysbus_init_irq(dev, &s->irq);
+    
+    qdev_init_gpio_in (&s->busdev.qdev, ftsdc010_handle_ack, 1);
+    qdev_init_gpio_out(&s->busdev.qdev, &s->req, 1);
+    
+    dinfo = drive_get_next(IF_SD);
+    s->card = sd_init(dinfo ? dinfo->bdrv : NULL, 0);
+
+    s->status = STR_CARD_REMOVED;
+    if (dinfo) {
+        if (bdrv_is_read_only(dinfo->bdrv))
+            s->status |= STR_WPROT;
+        if (bdrv_is_inserted(dinfo->bdrv))
+            s->status &= ~STR_CARD_REMOVED;
+    }
+    
+    return 0;
+}
+
+static const VMStateDescription vmstate_ftsdc010 = {
+    .name = "ftsdc010",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(cmd, ftsdc010_state),
+        VMSTATE_UINT32(arg, ftsdc010_state),
+        VMSTATE_UINT32_ARRAY(rsp, ftsdc010_state, 4),
+        VMSTATE_UINT32(rspcmd, ftsdc010_state),
+        VMSTATE_UINT32(dcr, ftsdc010_state),
+        VMSTATE_UINT32(dtr, ftsdc010_state),
+        VMSTATE_UINT32(dlr, ftsdc010_state),
+        VMSTATE_UINT32(datacnt, ftsdc010_state),
+        VMSTATE_UINT32(status, ftsdc010_state),
+        VMSTATE_UINT32(ier, ftsdc010_state),
+        VMSTATE_UINT32(pwr, ftsdc010_state),
+        VMSTATE_UINT32(clk, ftsdc010_state),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void ftsdc010_class_init(ObjectClass *klass, void *data)
+{
+    SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+    DeviceClass *k = DEVICE_CLASS(klass);
+
+    sdc->init  = ftsdc010_init;
+    k->vmsd    = &vmstate_ftsdc010;
+    k->reset   = ftsdc010_reset;
+    k->no_user = 1;
+}
+
+static TypeInfo ftsdc010_info = {
+    .name           = "ftsdc010",
+    .parent         = TYPE_SYS_BUS_DEVICE,
+    .instance_size  = sizeof(ftsdc010_state),
+    .class_init     = ftsdc010_class_init,
+};
+
+static void ftsdc010_register_types(void)
+{
+    type_register_static(&ftsdc010_info);
+}
+
+type_init(ftsdc010_register_types)
diff --git a/hw/ftsdc010.h b/hw/ftsdc010.h
new file mode 100644
index 0000000..482e6e2
--- /dev/null
+++ b/hw/ftsdc010.h
@@ -0,0 +1,103 @@ 
+/*
+ * (C) Copyright 2010
+ * Faraday Technology Inc. <www.faraday-tech.com>
+ * Dante Su <dantesu@gmail.com>
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#ifndef _FTSDC010_H
+#define _FTSDC010_H
+
+/* sd controller register */
+#define REG_CMD                0x0000
+#define REG_ARG                0x0004
+#define REG_RSP0               0x0008    /* response */
+#define REG_RSP1               0x000C
+#define REG_RSP2               0x0010
+#define REG_RSP3               0x0014
+#define REG_RSPCMD             0x0018    /* responsed command */
+#define REG_DCR                0x001C    /* data control */
+#define REG_DTR                0x0020    /* data timeout */
+#define REG_DLR                0x0024    /* data length */
+#define REG_STR                0x0028    /* status register */
+#define REG_SCR                0x002C    /* status clear register */
+#define REG_IER                0x0030    /* interrupt mask/enable register */
+#define REG_PWR                0x0034    /* power control */
+#define REG_CLK                0x0038    /* clock control */
+#define REG_BUS                0x003C    /* bus width */
+#define REG_DWR                0x0040    /* data window */
+#define REG_GPOR               0x0048
+#define REG_FEA                0x009C
+#define REG_REV                0x00A0
+
+/* bit mapping of command register */
+#define CMD_IDX                0x0000003F
+#define CMD_WAIT_RSP           0x00000040
+#define CMD_LONG_RSP           0x00000080
+#define CMD_APP                0x00000100
+#define CMD_EN                 0x00000200
+#define CMD_RST                0x00000400
+
+/* bit mapping of response command register */
+#define RSP_CMDIDX             0x0000003F
+#define RSP_CMDAPP             0x00000040
+
+/* bit mapping of data control register */
+#define DCR_BKSZ               0x0000000F
+#define DCR_WR                 0x00000010
+#define DCR_RD                 0x00000000
+#define DCR_DMA                0x00000020
+#define DCR_EN                 0x00000040
+#define DCR_THRES              0x00000080
+#define DCR_BURST1             0x00000000
+#define DCR_BURST4             0x00000100
+#define DCR_BURST8             0x00000200
+#define DCR_FIFO_RESET         0x00000400
+
+/* bit mapping of status register */
+#define STR_RSP_CRC            0x00000001
+#define STR_DAT_CRC            0x00000002
+#define STR_RSP_TIMEOUT        0x00000004
+#define STR_DAT_TIMEOUT        0x00000008
+#define STR_RSP_ERR            (STR_RSP_CRC | STR_RSP_TIMEOUT)
+#define STR_DAT_ERR            (STR_DAT_CRC | STR_DAT_TIMEOUT)
+#define STR_RSP                0x00000010
+#define STR_DAT                0x00000020
+#define STR_CMD                0x00000040
+#define STR_DAT_END            0x00000080
+#define STR_TXRDY              0x00000100
+#define STR_RXRDY              0x00000200
+#define STR_CARD_CHANGE        0x00000400
+#define STR_CARD_REMOVED       0x00000800
+#define STR_WPROT              0x00001000
+#define STR_SDIO               0x00010000
+#define STR_DAT0               0x00020000
+
+/* bit mapping of clock register */
+#define CLK_HISPD              0x00000200
+#define CLK_OFF                0x00000100
+#define CLK_SD                 0x00000080
+
+/* bit mapping of bus register */
+#define BUS_CARD_DATA3         0x00000020
+#define BUS_4BITS_SUPP         0x00000008
+#define BUS_8BITS_SUPP         0x00000010
+
+#endif