From: Kuo-Jung Su <dant...@faraday-tech.com> The FTSDC010 is a simple MMC/SD host controller and many of its registers are similar to Arm PrimeCell PL181.
Signed-off-by: Kuo-Jung Su <dant...@faraday-tech.com> --- hw/arm/Makefile.objs | 1 + hw/arm/faraday_a369_soc.c | 7 + hw/arm/ftsdc010.c | 354 +++++++++++++++++++++++++++++++++++++++++++++ hw/arm/ftsdc010.h | 90 ++++++++++++ 4 files changed, 452 insertions(+) create mode 100644 hw/arm/ftsdc010.c create mode 100644 hw/arm/ftsdc010.h diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs index 9fdefc8..0920270 100644 --- a/hw/arm/Makefile.objs +++ b/hw/arm/Makefile.objs @@ -51,3 +51,4 @@ obj-y += ftssp010.o obj-y += ftgmac100.o obj-y += ftlcdc200.o obj-y += fttsc010.o +obj-y += ftsdc010.o diff --git a/hw/arm/faraday_a369_soc.c b/hw/arm/faraday_a369_soc.c index 2177616..9b4eb7a 100644 --- a/hw/arm/faraday_a369_soc.c +++ b/hw/arm/faraday_a369_soc.c @@ -282,6 +282,13 @@ a369soc_device_init(FaradaySoCState *s) /* fttsc010 */ sysbus_create_simple("fttsc010", 0x92400000, s->pic[19]); + + /* ftsdc010 */ + ds = sysbus_create_simple("ftsdc010", 0x90600000, s->pic[39]); + ack = qdev_get_gpio_in(ds, 0); + req = qdev_get_gpio_in(s->hdma[0], 13); + qdev_connect_gpio_out(s->hdma[0], 13, ack); + qdev_connect_gpio_out(ds, 0, req); } static void a369soc_realize(DeviceState *dev, Error **errp) diff --git a/hw/arm/ftsdc010.c b/hw/arm/ftsdc010.c new file mode 100644 index 0000000..e60e384 --- /dev/null +++ b/hw/arm/ftsdc010.c @@ -0,0 +1,354 @@ +/* + * QEMU model of the FTSDC010 MMC/SD Host Controller + * + * Copyright (C) 2012 Faraday Technology + * Written by Dante Su <dant...@faraday-tech.com> + * + * This file is licensed under GNU GPL v2+. + */ + +#include "hw/sysbus.h" +#include "hw/sd.h" +#include "sysemu/sysemu.h" +#include "sysemu/blockdev.h" + +#include "ftsdc010.h" + +#define TYPE_FTSDC010 "ftsdc010" + +typedef struct Ftsdc010State { + 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; +} Ftsdc010State; + +#define FTSDC010(obj) \ + OBJECT_CHECK(Ftsdc010State, obj, TYPE_FTSDC010) + +static void ftsdc010_update_irq(Ftsdc010State *s) +{ + qemu_set_irq(s->irq, !!(s->ier & s->status)); +} + +static void ftsdc010_handle_ack(void *opaque, int line, int level) +{ + Ftsdc010State *s = FTSDC010(opaque); + + if (!(s->dcr & DCR_DMA)) { + return; + } + + if (level) { + qemu_set_irq(s->req, 0); + } else if (s->datacnt) { + qemu_set_irq(s->req, 1); + } +} + +static void ftsdc010_send_command(Ftsdc010State *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 & CMD_APP) ? RSP_CMDAPP : 0; + s->status |= SR_RSP; +#undef RWORD + } else { + s->status |= SR_CMD; + } + + if ((s->dcr & DCR_DMA) && s->datacnt) { + qemu_set_irq(s->req, 1); + } + + return; + +error: + s->status |= SR_RSP_TIMEOUT; +} + +static void ftsdc010_chip_reset(Ftsdc010State *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 &= ~(SR_CARD_REMOVED | SR_WPROT); + s->status |= SR_TXRDY | SR_RXRDY; + s->ier = 0; + s->pwr = 0; + s->clk = 0; +} + +static uint64_t ftsdc010_mem_read(void *opaque, hwaddr addr, unsigned size) +{ + Ftsdc010State *s = FTSDC010(opaque); + uint32_t i, ret = 0; + + switch (addr) { + case REG_SR: + return s->status; + case REG_DR: + if (!(s->dcr & DCR_WR) && s->datacnt && sd_data_ready(s->card)) { + for (i = 0; i < 4 && s->datacnt; i++, s->datacnt--) { + ret = deposit32(ret, i * 8, 8, sd_read_data(s->card)); + } + if (!s->datacnt) { + s->status |= SR_DAT_END; + } + s->status |= SR_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; /* support 1-bit/4-bit bus */ + case REG_FEAR: + return 0x00000010; /* fifo = 16 */ + case REG_REVR: + return 0x00030300; /* rev. 3.3.0 */ + default: + qemu_log_mask(LOG_GUEST_ERROR, + "ftsdc010: undefined memory access@%#" HWADDR_PRIx "\n", addr); + break; + } + + return ret; +} + +static void +ftsdc010_mem_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) +{ + int i; + Ftsdc010State *s = FTSDC010(opaque); + + switch (addr) { + case REG_DR: + if ((s->dcr & DCR_WR) && s->datacnt) { + for (i = 0; i < 4 && s->datacnt; i++, s->datacnt--) { + sd_write_data(s->card, extract32((uint32_t)val, i * 8, 8)); + } + if (!s->datacnt) { + s->status |= SR_DAT_END; + } + s->status |= SR_DAT; + } + break; + case REG_CMD: + s->cmd = (uint32_t)val; + if (s->cmd & CMD_RST) { + ftsdc010_chip_reset(s); + } else if (s->cmd & CMD_EN) { + s->cmd &= ~CMD_EN; + s->status &= ~(SR_CMD | SR_RSP | SR_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 &= ~(SR_DAT | SR_DAT_END | SR_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: + qemu_log_mask(LOG_GUEST_ERROR, + "ftsdc010: undefined memory access@%#" HWADDR_PRIx "\n", addr); + break; + } +} + +static const MemoryRegionOps mmio_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 *ds) +{ + Ftsdc010State *s = FTSDC010(SYS_BUS_DEVICE(ds)); + + 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 void ftsdc010_realize(DeviceState *dev, Error **errp) +{ + Ftsdc010State *s = FTSDC010(dev); + DriveInfo *dinfo; + + memory_region_init_io(&s->iomem, &mmio_ops, s, TYPE_FTSDC010, 0x1000); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem); + sysbus_init_irq(SYS_BUS_DEVICE(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 = SR_CARD_REMOVED; + if (dinfo) { + if (bdrv_is_read_only(dinfo->bdrv)) { + s->status |= SR_WPROT; + } + if (bdrv_is_inserted(dinfo->bdrv)) { + s->status &= ~SR_CARD_REMOVED; + } + } +} + +static const VMStateDescription vmstate_ftsdc010 = { + .name = TYPE_FTSDC010, + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(cmd, Ftsdc010State), + VMSTATE_UINT32(arg, Ftsdc010State), + VMSTATE_UINT32_ARRAY(rsp, Ftsdc010State, 4), + VMSTATE_UINT32(rspcmd, Ftsdc010State), + VMSTATE_UINT32(dcr, Ftsdc010State), + VMSTATE_UINT32(dtr, Ftsdc010State), + VMSTATE_UINT32(dlr, Ftsdc010State), + VMSTATE_UINT32(datacnt, Ftsdc010State), + VMSTATE_UINT32(status, Ftsdc010State), + VMSTATE_UINT32(ier, Ftsdc010State), + VMSTATE_UINT32(pwr, Ftsdc010State), + VMSTATE_UINT32(clk, Ftsdc010State), + VMSTATE_END_OF_LIST() + } +}; + +static void ftsdc010_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->vmsd = &vmstate_ftsdc010; + dc->reset = ftsdc010_reset; + dc->realize = ftsdc010_realize; + dc->no_user = 1; +} + +static const TypeInfo ftsdc010_info = { + .name = TYPE_FTSDC010, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Ftsdc010State), + .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/arm/ftsdc010.h b/hw/arm/ftsdc010.h new file mode 100644 index 0000000..8aee9c3 --- /dev/null +++ b/hw/arm/ftsdc010.h @@ -0,0 +1,90 @@ +/* + * QEMU model of the FTSDC010 MMC/SD Host Controller + * + * Copyright (C) 2012 Faraday Technology + * Written by Dante Su <dant...@faraday-tech.com> + * + * This file is licensed under GNU GPL v2+. + */ + +#ifndef HW_ARM_FTSDC010_H +#define HW_ARM_FTSDC010_H + +#include "qemu/bitops.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_SR 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_DR 0x0040 /* data register */ +#define REG_GPOR 0x0048 /* general purpose output register */ +#define REG_FEAR 0x009C /* feature register */ +#define REG_REVR 0x00A0 /* revision register */ + +/* bit mapping of command register */ +#define CMD_IDX 0x0000003F /* command code */ +#define CMD_WAIT_RSP 0x00000040 /* 32-bit response */ +#define CMD_LONG_RSP 0x00000080 /* 128-bit response */ +#define CMD_APP 0x00000100 /* command is a ACMD */ +#define CMD_EN 0x00000200 /* command enabled */ +#define CMD_RST 0x00000400 /* software reset */ + +/* 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 SR_RSP_CRC 0x00000001 +#define SR_DAT_CRC 0x00000002 +#define SR_RSP_TIMEOUT 0x00000004 +#define SR_DAT_TIMEOUT 0x00000008 +#define SR_RSP_ERR (SR_RSP_CRC | SR_RSP_TIMEOUT) +#define SR_DAT_ERR (SR_DAT_CRC | SR_DAT_TIMEOUT) +#define SR_RSP 0x00000010 +#define SR_DAT 0x00000020 +#define SR_CMD 0x00000040 +#define SR_DAT_END 0x00000080 +#define SR_TXRDY 0x00000100 +#define SR_RXRDY 0x00000200 +#define SR_CARD_CHANGE 0x00000400 +#define SR_CARD_REMOVED 0x00000800 +#define SR_WPROT 0x00001000 +#define SR_SDIO 0x00010000 +#define SR_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 -- 1.7.9.5