From: Kuo-Jung Su <dant...@faraday-tech.com> The FTSDC010 functions as the master in an SD memory card interface. It controls the communication between the AHB/APB bus and the SD card. Its core supports the SD bus of the SD/SDIO operations and the MMC bus of the MMC operation as well.
Signed-off-by: Kuo-Jung Su <dant...@faraday-tech.com> --- hw/ftsdc010.c | 363 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/ftsdc010.h | 88 ++++++++++++++ 2 files changed, 451 insertions(+) create mode 100644 hw/ftsdc010.c create mode 100644 hw/ftsdc010.h diff --git a/hw/ftsdc010.c b/hw/ftsdc010.c new file mode 100644 index 0000000..36a6ed3 --- /dev/null +++ b/hw/ftsdc010.c @@ -0,0 +1,363 @@ +/* + * 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 "sysbus.h" +#include "sysemu/sysemu.h" +#include "sysemu/blockdev.h" +#include "sd.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; +} ftsdc010_state; + +#define FTSDC010(obj) \ + OBJECT_CHECK(ftsdc010_state, obj, TYPE_FTSDC010) + +static 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(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 size) +{ + ftsdc010_state *s = FTSDC010(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 size) +{ + ftsdc010_state *s = FTSDC010(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_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) +{ + SysBusDevice *busdev = SYS_BUS_DEVICE(ds); + ftsdc010_state *s = FTSDC010(FROM_SYSBUS(ftsdc010_state, busdev)); + + 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 = FTSDC010(FROM_SYSBUS(ftsdc010_state, dev)); + DriveInfo *dinfo; + + memory_region_init_io(&s->iomem, &ftsdc010_ops, s, TYPE_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 = TYPE_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 *k = SYS_BUS_DEVICE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + k->init = ftsdc010_init; + dc->vmsd = &vmstate_ftsdc010; + dc->reset = ftsdc010_reset; + dc->no_user = 1; +} + +static const TypeInfo ftsdc010_info = { + .name = TYPE_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..26aa5a9 --- /dev/null +++ b/hw/ftsdc010.h @@ -0,0 +1,88 @@ +/* + * 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. + */ + +#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 -- 1.7.9.5