@@ -11,6 +11,7 @@ obj-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
obj-y += exynos4210_gic.o exynos4210_combiner.o exynos4210.o
obj-y += exynos4_boards.o exynos4210_uart.o exynos4210_pwm.o
obj-y += exynos4210_pmu.o exynos4210_mct.o exynos4210_fimd.o
+obj-y += exynos4210_sdhci.o
obj-y += arm_l2x0.o
obj-y += arm_mptimer.o a15mpcore.o
obj-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
@@ -56,6 +56,12 @@
#define EXYNOS4210_EXT_COMBINER_BASE_ADDR 0x10440000
#define EXYNOS4210_INT_COMBINER_BASE_ADDR 0x10448000
+/* SD/MMC host controllers SFR base addresses */
+#define EXYNOS4210_SDHC0_BASE_ADDR 0x12510000
+#define EXYNOS4210_SDHC1_BASE_ADDR 0x12520000
+#define EXYNOS4210_SDHC2_BASE_ADDR 0x12530000
+#define EXYNOS4210_SDHC3_BASE_ADDR 0x12540000
+
/* PMU SFR base address */
#define EXYNOS4210_PMU_BASE_ADDR 0x10020000
@@ -290,6 +296,20 @@ Exynos4210State *exynos4210_init(MemoryRegion *system_mem,
EXYNOS4210_UART3_FIFO_SIZE, 3, NULL,
s->irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 3)]);
+ /*** SD/MMC host controllers ***/
+
+ sysbus_create_simple("exynos4210.sdhci", EXYNOS4210_SDHC0_BASE_ADDR,
+ s->irq_table[exynos4210_get_irq(29, 0)]);
+
+ sysbus_create_simple("exynos4210.sdhci", EXYNOS4210_SDHC1_BASE_ADDR,
+ s->irq_table[exynos4210_get_irq(29, 1)]);
+
+ sysbus_create_simple("exynos4210.sdhci", EXYNOS4210_SDHC2_BASE_ADDR,
+ s->irq_table[exynos4210_get_irq(29, 2)]);
+
+ sysbus_create_simple("exynos4210.sdhci", EXYNOS4210_SDHC3_BASE_ADDR,
+ s->irq_table[exynos4210_get_irq(29, 3)]);
+
/*** Display controller (FIMD) ***/
sysbus_create_varargs("exynos4210.fimd", EXYNOS4210_FIMD0_BASE_ADDR,
s->irq_table[exynos4210_get_irq(11, 0)],
new file mode 100644
@@ -0,0 +1,438 @@
+/*
+ * Samsung Exynos4210 SD/MMC host controller model
+ *
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ * Mitsyanko Igor <i.mitsyanko@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "sdhci.h"
+
+#define EXYNOS4_SDHC_CAPABILITIES 0x05E80080
+#define EXYNOS4_SDHC_MAX_BUFSZ 512
+
+#define EXYNOS4_SDHC_DEBUG 0
+
+#if EXYNOS4_SDHC_DEBUG == 0
+ #define DPRINT_L1(fmt, args...) do { } while (0)
+ #define DPRINT_L2(fmt, args...) do { } while (0)
+ #define ERRPRINT(fmt, args...) do { } while (0)
+#elif EXYNOS4_SDHC_DEBUG == 1
+ #define DPRINT_L1(fmt, args...) \
+ do {fprintf(stderr, "QEMU SDHC: "fmt, ## args); } while (0)
+ #define DPRINT_L2(fmt, args...) do { } while (0)
+ #define ERRPRINT(fmt, args...) \
+ do {fprintf(stderr, "QEMU SDHC ERROR: "fmt, ## args); } while (0)
+#else
+ #define DPRINT_L1(fmt, args...) \
+ do {fprintf(stderr, "QEMU SDHC: "fmt, ## args); } while (0)
+ #define DPRINT_L2(fmt, args...) \
+ do {fprintf(stderr, "QEMU SDHC: "fmt, ## args); } while (0)
+ #define ERRPRINT(fmt, args...) \
+ do {fprintf(stderr, "QEMU SDHC ERROR: "fmt, ## args); } while (0)
+#endif
+
+
+#define TYPE_EXYNOS4_SDHC "exynos4210.sdhci"
+#define EXYNOS4_SDHCI(obj) \
+ OBJECT_CHECK(Exynos4SDHCIState, (obj), TYPE_EXYNOS4_SDHC)
+
+/* ADMA Error Status Register */
+#define EXYNOS4_SDHC_FINAL_BLOCK (1 << 10)
+#define EXYNOS4_SDHC_CONTINUE_REQ (1 << 9)
+#define EXYNOS4_SDHC_IRQ_STAT (1 << 8)
+/* Control register 2 */
+#define EXYNOS4_SDHC_CONTROL2 0x80
+#define EXYNOS4_SDHC_HWINITFIN (1 << 0)
+#define EXYNOS4_SDHC_DISBUFRD (1 << 6)
+#define EXYNOS4_SDHC_SDOPSIGPC (1 << 12)
+#define EXYNOS4_SDHC_SDINPSIGPC (1 << 3)
+/* Control register 3 */
+#define EXYNOS4_SDHC_CONTROL3 0x84
+/* Control register 4 */
+#define EXYNOS4_SDHC_CONTROL4 0x8C
+/* Clock control register */
+#define EXYNOS4_SDHC_SDCLK_STBL (1 << 3)
+
+typedef struct Exynos4SDHCIState {
+ SDHCIState sdhci;
+
+ uint32_t admaerr;
+ uint32_t control2;
+ uint32_t control3;
+ bool stoped_adma;
+} Exynos4SDHCIState;
+
+static void exynos4210_sdhci_reset(DeviceState *d)
+{
+ Exynos4SDHCIState *s = EXYNOS4_SDHCI(d);
+
+ SDHCI_GET_CLASS(d)->reset(SDHCI(d));
+ s->stoped_adma = false;
+ s->admaerr = 0;
+ s->control2 = 0;
+ s->control3 = 0x7F5F3F1F;
+}
+
+static void exynos4210_sdhci_start_adma(SDHCIState *sdhci)
+{
+ Exynos4SDHCIState *s = EXYNOS4_SDHCI(sdhci);
+ unsigned int length, n, begin;
+ target_phys_addr_t entry_addr;
+ uint32_t addr;
+ uint8_t attributes;
+ const uint16_t block_size = sdhci->blksize & 0x0fff;
+ s->admaerr &=
+ ~(EXYNOS4_SDHC_FINAL_BLOCK | SDHC_ADMAERR_LENGTH_MISMATCH);
+
+ while (1) {
+ addr = length = attributes = 0;
+ entry_addr = (target_phys_addr_t)(sdhci->admasysaddr & 0xFFFFFFFFull);
+
+ /* fetch next entry from descriptor table */
+ cpu_physical_memory_read(entry_addr + 4, (uint8_t *)(&addr), 4);
+ cpu_physical_memory_read(entry_addr + 2, (uint8_t *)(&length), 2);
+ cpu_physical_memory_read(entry_addr, (uint8_t *)(&attributes), 1);
+ DPRINT_L1("ADMA loop: addr=0x%08x, len=%d, attr=%x\n",
+ addr, length, attributes);
+
+ if ((attributes & SDHC_ADMA_ATTR_VALID) == 0) {
+ /* Indicate that error occurred in ST_FDS state */
+ s->admaerr &= ~SDHC_ADMAERR_STATE_MASK;
+ s->admaerr |= SDHC_ADMAERR_STATE_ST_FDS;
+ DPRINT_L1("ADMA not valid at addr=0x%lx\n", sdhci->admasysaddr);
+
+ if (sdhci->errintstsen & SDHC_EISEN_ADMAERR) {
+ sdhci->errintsts |= SDHC_EIS_ADMAERR;
+ sdhci->norintsts |= SDHC_NIS_ERR;
+ }
+
+ if (sdhci->errintsigen & sdhci->errintsts) {
+ sdhci->slotint = 1;
+ }
+
+ qemu_set_irq(sdhci->irq, sdhci->errintsigen & sdhci->errintsts);
+ break;
+ }
+
+ if (length == 0) {
+ length = 65536;
+ }
+
+ addr &= 0xfffffffc; /* minimum unit of addr is 4 byte */
+
+ switch (attributes & SDHC_ADMA_ATTR_ACT_MASK) {
+ case SDHC_ADMA_ATTR_ACT_TRAN: /* data transfer */
+ if (sdhci->trnmod & SDHC_TRNS_READ) {
+ while (length) {
+ if (sdhci->data_count == 0) {
+ for (n = 0; n < block_size; n++) {
+ sdhci->fifo_buffer[n] = sd_read_data(sdhci->card);
+ }
+ }
+ begin = sdhci->data_count;
+ if ((length + begin) < block_size) {
+ sdhci->data_count = length + begin;
+ length = 0;
+ } else {
+ sdhci->data_count = block_size;
+ length -= block_size - begin;
+ }
+ cpu_physical_memory_write(addr, &sdhci->fifo_buffer[begin],
+ sdhci->data_count - begin);
+ addr += sdhci->data_count - begin;
+ if (sdhci->data_count == block_size) {
+ sdhci->data_count = 0;
+ if (sdhci->trnmod & SDHC_TRNS_BLK_CNT_EN) {
+ sdhci->blkcnt--;
+ if (sdhci->blkcnt == 0) {
+ break;
+ }
+ }
+ }
+ }
+ } else {
+ while (length) {
+ begin = sdhci->data_count;
+ if ((length + begin) < block_size) {
+ sdhci->data_count = length + begin;
+ length = 0;
+ } else {
+ sdhci->data_count = block_size;
+ length -= block_size - begin;
+ }
+ cpu_physical_memory_read(addr,
+ &sdhci->fifo_buffer[begin], sdhci->data_count);
+ addr += sdhci->data_count - begin;
+ if (sdhci->data_count == block_size) {
+ for (n = 0; n < block_size; n++) {
+ sd_write_data(sdhci->card, sdhci->fifo_buffer[n]);
+ }
+ sdhci->data_count = 0;
+ if (sdhci->trnmod & SDHC_TRNS_BLK_CNT_EN) {
+ sdhci->blkcnt--;
+ if (sdhci->blkcnt == 0) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ sdhci->admasysaddr += 8;
+ break;
+ case SDHC_ADMA_ATTR_ACT_LINK: /* link to next descriptor table */
+ sdhci->admasysaddr = addr;
+ DPRINT_L1("ADMA link: admasysaddr=0x%lx\n", sdhci->admasysaddr);
+ break;
+ default:
+ sdhci->admasysaddr += 8;
+ break;
+ }
+
+ /* ADMA transfer terminates if blkcnt == 0 or by END attribute */
+ if (((sdhci->trnmod & SDHC_TRNS_BLK_CNT_EN) &&
+ (sdhci->blkcnt == 0)) || (attributes & SDHC_ADMA_ATTR_END)) {
+ DPRINT_L2("ADMA transfer completed\n");
+ if (length || ((attributes & SDHC_ADMA_ATTR_END) &&
+ (sdhci->trnmod & SDHC_TRNS_BLK_CNT_EN) && sdhci->blkcnt != 0) ||
+ ((sdhci->trnmod & SDHC_TRNS_BLK_CNT_EN) && sdhci->blkcnt == 0 &&
+ (attributes & SDHC_ADMA_ATTR_END) == 0)) {
+ ERRPRINT("ADMA length mismatch\n");
+ s->admaerr |= SDHC_ADMAERR_LENGTH_MISMATCH |
+ SDHC_ADMAERR_STATE_ST_TFR;
+ if (sdhci->errintstsen & SDHC_EISEN_ADMAERR) {
+ sdhci->errintsts |= SDHC_EIS_ADMAERR;
+ sdhci->norintsts |= SDHC_NIS_ERR;
+ }
+
+ if (sdhci->errintsigen & sdhci->errintsts) {
+ sdhci->slotint = 1;
+ }
+
+ qemu_set_irq(sdhci->irq, sdhci->errintsigen & sdhci->errintsts);
+ }
+
+ s->admaerr |= EXYNOS4_SDHC_FINAL_BLOCK;
+ SDHCI_GET_CLASS(sdhci)->end_data_transfer(sdhci);
+ break;
+ }
+
+ if (attributes & SDHC_ADMA_ATTR_INT) {
+ DPRINT_L1("ADMA interrupt: addr=0x%lx\n", sdhci->admasysaddr);
+ s->admaerr |= EXYNOS4_SDHC_IRQ_STAT;
+ s->stoped_adma = true;
+ if (sdhci->norintstsen & SDHC_NISEN_DMA) {
+ sdhci->norintsts |= SDHC_NIS_DMA;
+ }
+ if (sdhci->norintsts & sdhci->norintsigen) {
+ sdhci->slotint = 1;
+ }
+ qemu_set_irq(sdhci->irq, sdhci->norintsigen & sdhci->norintsts);
+ break;
+ }
+ }
+}
+
+static bool exynos4210_sdhci_can_issue_command(SDHCIState *sdhci)
+{
+ Exynos4SDHCIState *s = EXYNOS4_SDHCI(sdhci);
+
+ if (!SDHC_CLOCK_IS_ON(sdhci->clkcon) || (!(sdhci->pwrcon & SDHC_POWER_ON) &&
+ (s->control2 & (EXYNOS4_SDHC_SDOPSIGPC | EXYNOS4_SDHC_SDINPSIGPC))) ||
+ (((sdhci->prnsts & SDHC_DATA_INHIBIT) || sdhci->stoped_state) &&
+ ((sdhci->cmdreg & SDHC_CMD_DATA_PRESENT) ||
+ ((sdhci->cmdreg & SDHC_CMD_RESPONSE) == SDHC_CMD_RSP_WITH_BUSY &&
+ !(SDHC_COMMAND_TYPE(sdhci->cmdreg) == SDHC_CMD_ABORT))))) {
+ return false;
+ }
+
+ return true;
+}
+
+static uint64_t
+exynos4210_sdhci_readfn(void *opaque, target_phys_addr_t offset, unsigned size)
+{
+ Exynos4SDHCIState *s = (Exynos4SDHCIState *)opaque;
+ uint32_t ret;
+
+ switch (offset & ~0x3) {
+ case SDHC_BDATA:
+ /* Buffer data port read can be disabled by CONTROL2 register */
+ if (s->control2 & EXYNOS4_SDHC_DISBUFRD) {
+ ret = 0;
+ } else {
+ ret = SDHCI_GET_CLASS(s)->mem_read(SDHCI(s), offset, size);
+ }
+ break;
+ case SDHC_ADMAERR:
+ ret = (s->admaerr >> 8 * (offset - SDHC_ADMAERR)) &
+ ((1 << 8 * size) - 1);
+ break;
+ case EXYNOS4_SDHC_CONTROL2:
+ ret = (s->control2 >> 8 * (offset - EXYNOS4_SDHC_CONTROL2)) &
+ ((1 << 8 * size) - 1);
+ break;
+ case EXYNOS4_SDHC_CONTROL3:
+ ret = (s->control3 >> 8 * (offset - EXYNOS4_SDHC_CONTROL3)) &
+ ((1 << 8 * size) - 1);
+ break;
+ case EXYNOS4_SDHC_CONTROL4:
+ ret = 0;
+ break;
+ default:
+ ret = SDHCI_GET_CLASS(s)->mem_read(SDHCI(s), offset, size);
+ break;
+ }
+
+ DPRINT_L2("read %ub: addr[0x%04x] -> %u(0x%x)\n", size, offset, ret, ret);
+ return ret;
+}
+
+static void exynos4210_sdhci_writefn(void *opaque, target_phys_addr_t offset,
+ uint64_t val, unsigned size)
+{
+ Exynos4SDHCIState *s = (Exynos4SDHCIState *)opaque;
+ SDHCIState *sdhci = SDHCI(s);
+ unsigned shift;
+
+ DPRINT_L2("write %ub: addr[0x%04x] <- %u(0x%x)\n", size, (uint32_t)offset,
+ (uint32_t)val, (uint32_t)val);
+
+ switch (offset) {
+ case SDHC_CLKCON:
+ if ((val & SDHC_CLOCK_SDCLK_EN) &&
+ (sdhci->prnsts & SDHC_CARD_PRESENT)) {
+ val |= EXYNOS4_SDHC_SDCLK_STBL;
+ } else {
+ val &= ~EXYNOS4_SDHC_SDCLK_STBL;
+ }
+ break;
+ case EXYNOS4_SDHC_CONTROL2 ... EXYNOS4_SDHC_CONTROL2 + 3:
+ shift = (offset - EXYNOS4_SDHC_CONTROL2) * 8;
+ s->control2 = (s->control2 & ~(((1 << 8 * size) - 1) << shift)) |
+ (val << shift);
+ return;
+ case EXYNOS4_SDHC_CONTROL3 ... EXYNOS4_SDHC_CONTROL3 + 3:
+ shift = (offset - EXYNOS4_SDHC_CONTROL2) * 8;
+ s->control3 = (s->control3 & ~(((1 << 8 * size) - 1) << shift)) |
+ (val << shift);
+ return;
+ case SDHC_ADMAERR ... SDHC_ADMAERR + 3:
+ if (size == 4 || (size == 2 && offset == SDHC_ADMAERR) ||
+ (size == 1 && offset == (SDHC_ADMAERR + 1))) {
+ uint32_t mask = 0;
+
+ if (size == 2) {
+ mask = 0xFFFF0000;
+ } else if (size == 1) {
+ mask = 0xFFFF00FF;
+ val <<= 8;
+ }
+
+ s->admaerr = (s->admaerr & (mask | EXYNOS4_SDHC_FINAL_BLOCK |
+ EXYNOS4_SDHC_IRQ_STAT)) | (val & ~(EXYNOS4_SDHC_FINAL_BLOCK |
+ EXYNOS4_SDHC_IRQ_STAT | EXYNOS4_SDHC_CONTINUE_REQ));
+ s->admaerr &= ~(val & EXYNOS4_SDHC_IRQ_STAT);
+ if ((s->stoped_adma) && (val & EXYNOS4_SDHC_CONTINUE_REQ) &&
+ (SDHC_DMA_TYPE(sdhci->hostctl) == SDHC_CTRL_ADMA2_32)) {
+ s->stoped_adma = false;
+ SDHCI_GET_CLASS(sdhci)->do_adma(sdhci);
+ }
+ } else {
+ uint32_t mask = (1 << (size * 8)) - 1;
+ shift = 8 * (offset & 0x3);
+ val <<= shift;
+ mask = ~(mask << shift);
+ s->admaerr = (s->admaerr & mask) | val;
+ }
+ return;
+ }
+
+ SDHCI_GET_CLASS(s)->mem_write(sdhci, offset, val, size);
+}
+
+static const MemoryRegionOps exynos4210_sdhci_mmio_ops = {
+ .read = exynos4210_sdhci_readfn,
+ .write = exynos4210_sdhci_writefn,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ .unaligned = false
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const VMStateDescription exynos4210_sdhci_vmstate = {
+ .name = "exynos4210.sdhci",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(sdhci, Exynos4SDHCIState, 1, sdhci_vmstate, SDHCIState),
+ VMSTATE_UINT32(admaerr, Exynos4SDHCIState),
+ VMSTATE_UINT32(control2, Exynos4SDHCIState),
+ VMSTATE_UINT32(control3, Exynos4SDHCIState),
+ VMSTATE_BOOL(stoped_adma, Exynos4SDHCIState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int exynos4210_sdhci_realize(SysBusDevice *busdev)
+{
+ SDHCIState *sdhci = SDHCI(busdev);
+
+ sdhci->buf_maxsz = EXYNOS4_SDHC_MAX_BUFSZ;
+ sdhci->fifo_buffer = g_malloc0(sdhci->buf_maxsz);
+ sysbus_init_irq(busdev, &sdhci->irq);
+ memory_region_init_io(&sdhci->iomem, &exynos4210_sdhci_mmio_ops,
+ EXYNOS4_SDHCI(sdhci), "exynos4210.sdhci", SDHC_REGISTERS_MAP_SIZE);
+ sysbus_init_mmio(busdev, &sdhci->iomem);
+ return 0;
+}
+
+static Property exynos4210_sdhci_properties[] = {
+ DEFINE_PROP_HEX32("capareg", SDHCIState, capareg,
+ EXYNOS4_SDHC_CAPABILITIES),
+ DEFINE_PROP_HEX32("maxcurr", SDHCIState, maxcurr, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void exynos4210_sdhci_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *sbdc = SYS_BUS_DEVICE_CLASS(klass);
+ SDHCIClass *k = SDHCI_CLASS(klass);
+
+ dc->vmsd = &exynos4210_sdhci_vmstate;
+ dc->reset = exynos4210_sdhci_reset;
+ dc->props = exynos4210_sdhci_properties;
+ sbdc->init = exynos4210_sdhci_realize;
+
+ k->can_issue_command = exynos4210_sdhci_can_issue_command;
+ k->do_adma = exynos4210_sdhci_start_adma;
+}
+
+static const TypeInfo exynos4210_sdhci_type_info = {
+ .name = TYPE_EXYNOS4_SDHC,
+ .parent = TYPE_SDHCI,
+ .instance_size = sizeof(Exynos4SDHCIState),
+ .class_init = exynos4210_sdhci_class_init,
+};
+
+static void exynos4210_sdhci_register_types(void)
+{
+ type_register_static(&exynos4210_sdhci_type_info);
+}
+
+type_init(exynos4210_sdhci_register_types)