aboutsummaryrefslogtreecommitdiffstats
path: root/hw/goldfish_mmc.c
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:30:32 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:30:32 -0800
commit8b23a6c7e1aee255004dd19098d4c2462b61b849 (patch)
tree7a4d682ba51f0ff0364c5ca2509f515bdaf96de9 /hw/goldfish_mmc.c
parentf721e3ac031f892af46f255a47d7f54a91317b30 (diff)
downloadexternal_qemu-8b23a6c7e1aee255004dd19098d4c2462b61b849.zip
external_qemu-8b23a6c7e1aee255004dd19098d4c2462b61b849.tar.gz
external_qemu-8b23a6c7e1aee255004dd19098d4c2462b61b849.tar.bz2
auto import from //depot/cupcake/@135843
Diffstat (limited to 'hw/goldfish_mmc.c')
-rw-r--r--hw/goldfish_mmc.c468
1 files changed, 468 insertions, 0 deletions
diff --git a/hw/goldfish_mmc.c b/hw/goldfish_mmc.c
new file mode 100644
index 0000000..272f403
--- /dev/null
+++ b/hw/goldfish_mmc.c
@@ -0,0 +1,468 @@
+/* Copyright (C) 2007-2008 The Android Open Source Project
+**
+** This software is licensed under the terms of the GNU General Public
+** License version 2, as published by the Free Software Foundation, and
+** may be copied, distributed, and modified under those terms.
+**
+** 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.
+*/
+#include "qemu_file.h"
+#include "goldfish_device.h"
+#include "mmc.h"
+#include "sd.h"
+#include "block.h"
+
+enum {
+ /* status register */
+ MMC_INT_STATUS = 0x00,
+ /* set this to enable IRQ */
+ MMC_INT_ENABLE = 0x04,
+ /* set this to specify buffer address */
+ MMC_SET_BUFFER = 0x08,
+
+ /* MMC command number */
+ MMC_CMD = 0x0C,
+
+ /* MMC argument */
+ MMC_ARG = 0x10,
+
+ /* MMC response (or R2 bits 0 - 31) */
+ MMC_RESP_0 = 0x14,
+
+ /* MMC R2 response bits 32 - 63 */
+ MMC_RESP_1 = 0x18,
+
+ /* MMC R2 response bits 64 - 95 */
+ MMC_RESP_2 = 0x1C,
+
+ /* MMC R2 response bits 96 - 127 */
+ MMC_RESP_3 = 0x20,
+
+ MMC_BLOCK_LENGTH = 0x24,
+ MMC_BLOCK_COUNT = 0x28,
+
+ /* MMC state flags */
+ MMC_STATE = 0x2C,
+
+ /* MMC_INT_STATUS bits */
+
+ MMC_STAT_END_OF_CMD = 1U << 0,
+ MMC_STAT_END_OF_DATA = 1U << 1,
+ MMC_STAT_STATE_CHANGE = 1U << 2,
+
+ /* MMC_STATE bits */
+ MMC_STATE_INSERTED = 1U << 0,
+ MMC_STATE_READ_ONLY = 1U << 1,
+};
+
+
+struct goldfish_mmc_state {
+ struct goldfish_device dev;
+ BlockDriverState *bs;
+ // pointer to our buffer
+ uint8_t* buffer;
+ // offsets for read and write operations
+ uint32_t read_offset, write_offset;
+ // buffer status flags
+ uint32_t int_status;
+ // irq enable mask for int_status
+ uint32_t int_enable;
+
+ // MMC command argument
+ uint32_t arg;
+ uint32_t resp[4];
+
+ uint32_t block_length;
+ uint32_t block_count;
+ int is_SDHC;
+};
+
+#define GOLDFISH_MMC_SAVE_VERSION 1
+#define QFIELD_STRUCT struct goldfish_mmc_state
+QFIELD_BEGIN(goldfish_mmc_fields)
+ QFIELD_INT32(read_offset),
+ QFIELD_INT32(write_offset),
+ QFIELD_INT32(int_status),
+ QFIELD_INT32(int_enable),
+ QFIELD_INT32(arg),
+ QFIELD_INT32(resp[0]),
+ QFIELD_INT32(resp[1]),
+ QFIELD_INT32(resp[2]),
+ QFIELD_INT32(resp[3]),
+ QFIELD_INT32(block_length),
+ QFIELD_INT32(block_count),
+ QFIELD_INT32(is_SDHC),
+QFIELD_END
+
+static void goldfish_mmc_save(QEMUFile* f, void* opaque)
+{
+ struct goldfish_mmc_state* s = opaque;
+
+ qemu_put_be32(f, s->buffer - phys_ram_base);
+ qemu_put_struct(f, goldfish_mmc_fields, s);
+}
+
+static int goldfish_mmc_load(QEMUFile* f, void* opaque, int version_id)
+{
+ struct goldfish_mmc_state* s = opaque;
+
+ if (version_id != GOLDFISH_MMC_SAVE_VERSION)
+ return -1;
+
+ s->buffer = qemu_get_be32(f) + phys_ram_base;
+ return qemu_get_struct(f, goldfish_mmc_fields, s);
+}
+
+struct mmc_opcode {
+ const char* name;
+ int cmd;
+} mmc_opcodes[] = {
+ { "MMC_GO_IDLE_STATE", 0 },
+ { "MMC_SEND_OP_COND", 1 },
+ { "MMC_ALL_SEND_CID", 2 },
+ { "MMC_SET_RELATIVE_ADDR", 3 },
+ { "MMC_SET_DSR", 4 },
+ { "MMC_SWITCH", 6 },
+ { "MMC_SELECT_CARD", 7 },
+ { "MMC_SEND_EXT_CSD", 8 },
+ { "MMC_SEND_CSD", 9 },
+ { "MMC_SEND_CID", 10 },
+ { "MMC_READ_DAT_UNTIL_STOP", 11 },
+ { "MMC_STOP_TRANSMISSION", 12 },
+ { "MMC_SEND_STATUS", 13 },
+ { "MMC_GO_INACTIVE_STATE", 15 },
+ { "MMC_SET_BLOCKLEN", 16 },
+ { "MMC_READ_SINGLE_BLOCK", 17 },
+ { "MMC_READ_MULTIPLE_BLOCK", 18 },
+ { "MMC_WRITE_DAT_UNTIL_STOP", 20 },
+ { "MMC_SET_BLOCK_COUNT", 23 },
+ { "MMC_WRITE_BLOCK", 24 },
+ { "MMC_WRITE_MULTIPLE_BLOCK", 25 },
+ { "MMC_PROGRAM_CID", 26 },
+ { "MMC_PROGRAM_CSD", 27 },
+ { "MMC_SET_WRITE_PROT", 28 },
+ { "MMC_CLR_WRITE_PROT", 29 },
+ { "MMC_SEND_WRITE_PROT", 30 },
+ { "MMC_ERASE_GROUP_START", 35 },
+ { "MMC_ERASE_GROUP_END", 36 },
+ { "MMC_ERASE", 38 },
+ { "MMC_FAST_IO", 39 },
+ { "MMC_GO_IRQ_STATE", 40 },
+ { "MMC_LOCK_UNLOCK", 42 },
+ { "MMC_APP_CMD", 55 },
+ { "MMC_GEN_CMD", 56 },
+ { "SD_APP_OP_COND", 41 },
+ { "SD_APP_SEND_SCR", 51 },
+ { "UNKNOWN", -1 }
+};
+
+#if 0
+static const char* get_command_name(int command)
+{
+ struct mmc_opcode* opcode = mmc_opcodes;
+
+ while (opcode->cmd != command && opcode->cmd != -1) opcode++;
+ return opcode->name;
+}
+#endif
+
+static void goldfish_mmc_do_command(struct goldfish_mmc_state *s, uint32_t cmd, uint32_t arg)
+{
+ int result;
+ int new_status = MMC_STAT_END_OF_CMD;
+ int opcode = cmd & 63;
+
+// fprintf(stderr, "goldfish_mmc_do_command opcode: %s (0x%04X), arg: %d\n", get_command_name(opcode), cmd, arg);
+
+ s->resp[0] = 0;
+ s->resp[1] = 0;
+ s->resp[2] = 0;
+ s->resp[3] = 0;
+
+#define SET_R1_CURRENT_STATE(s) ((s << 9) & 0x00001E00) /* sx, b (4 bits) */
+
+ switch (opcode) {
+ case MMC_SEND_CSD: {
+ int64_t sector_count = 0;
+ uint64_t capacity;
+ uint8_t exponent;
+ uint32_t m;
+
+ bdrv_get_geometry(s->bs, (uint64_t*)&sector_count);
+ capacity = sector_count * 512;
+ if (capacity > 2147483648U) {
+ // if storages is > 2 gig, then emulate SDHC card
+ s->is_SDHC = 1;
+
+ // CSD bits borrowed from a real SDHC card, with capacity bits zeroed out
+ s->resp[3] = 0x400E0032;
+ s->resp[2] = 0x5B590000;
+ s->resp[1] = 0x00007F80;
+ s->resp[0] = 0x0A4040DF;
+
+ // stuff in the real capacity
+ // m = UNSTUFF_BITS(resp, 48, 22);
+ m = (uint32_t)(capacity / (512*1024)) - 1;
+ // m must fit into 22 bits
+ if (m & 0xFFC00000) {
+ fprintf(stderr, "SD card too big (%lld bytes). Maximum SDHC card size is 128 gigabytes.\n", capacity);
+ abort();
+ }
+
+ // low 16 bits go in high end of resp[1]
+ s->resp[1] |= ((m & 0x0000FFFF) << 16);
+ // high 6 bits go in low end of resp[2]
+ s->resp[2] |= (m >> 16);
+ } else {
+ // emulate standard SD card
+ s->is_SDHC = 0;
+
+ // CSD bits borrowed from a real SD card, with capacity bits zeroed out
+ s->resp[3] = 0x00260032;
+ s->resp[2] = 0x5F5A8000;
+ s->resp[1] = 0x3EF84FFF;
+ s->resp[0] = 0x928040CB;
+
+ // stuff in the real capacity
+ // e = UNSTUFF_BITS(resp, 47, 3);
+ // m = UNSTUFF_BITS(resp, 62, 12);
+ // csd->capacity = (1 + m) << (e + 2);
+ // need to reverse the formula and calculate e and m
+ exponent = 0;
+ capacity = sector_count * 512;
+ if (capacity > 2147483648U) {
+ fprintf(stderr, "SD card too big (%lld bytes). Maximum SD card size is 2 gigabytes.\n", capacity);
+ abort();
+ }
+ capacity >>= 10; // convert to Kbytes
+ while (capacity > 4096) {
+ // (capacity - 1) must fit into 12 bits
+ exponent++;
+ capacity >>= 1;
+ }
+ capacity -= 1;
+ exponent -= 2;
+ if (exponent > 7)
+ cpu_abort(cpu_single_env, "exponent %d too big\n", exponent);
+
+ s->resp[2] |= (((uint32_t)capacity >> 2) & 0x3FF); // high 10 bits to bottom of resp[2]
+ s->resp[1] |= (((uint32_t)capacity & 3) << 30); // low 2 bits to top of resp[1]
+ s->resp[1] |= (exponent << (47 - 32));
+ }
+ break;
+ }
+
+ case MMC_SEND_EXT_CSD:
+ s->resp[0] = arg;
+ break;
+
+ case MMC_APP_CMD:
+ s->resp[0] = SET_R1_CURRENT_STATE(4) | R1_READY_FOR_DATA | R1_APP_CMD; //2336
+ break;
+
+ case SD_APP_OP_COND:
+ s->resp[0] = 0x80FF8000;
+ break;
+
+ case SD_APP_SEND_SCR:
+ {
+ uint32_t* scr = (uint32_t*)s->buffer;
+ scr[0] = 0x00002502;
+ scr[1] = 0x00000000;
+ s->resp[0] = SET_R1_CURRENT_STATE(4) | R1_READY_FOR_DATA | R1_APP_CMD; //2336
+ new_status |= MMC_STAT_END_OF_DATA;
+ break;
+ }
+ case MMC_SET_RELATIVE_ADDR:
+ s->resp[0] = -518519520;
+ break;
+
+ case MMC_ALL_SEND_CID:
+ s->resp[3] = 55788627;
+ s->resp[2] = 1429221959;
+ s->resp[1] = -2147479692;
+ s->resp[0] = -436179883;
+ break;
+
+ case MMC_SELECT_CARD:
+ s->resp[0] = SET_R1_CURRENT_STATE(3) | R1_READY_FOR_DATA; // 1792
+ break;
+
+ case MMC_SWITCH:
+ if (arg == 0x00FFFFF1 || arg == 0x80FFFFF1) {
+ uint8_t* switchbuf = s->buffer;
+ memset(switchbuf, 0, 64);
+ switchbuf[13] = 2;
+ new_status |= MMC_STAT_END_OF_DATA;
+ }
+ s->resp[0] = SET_R1_CURRENT_STATE(4) | R1_READY_FOR_DATA | R1_APP_CMD; //2336
+ break;
+
+ case MMC_SET_BLOCKLEN:
+ s->block_length = arg;
+ s->resp[0] = SET_R1_CURRENT_STATE(4) | R1_READY_FOR_DATA; // 2304
+ break;
+
+ case MMC_READ_SINGLE_BLOCK:
+ s->block_count = 1;
+ // fall through
+ case MMC_READ_MULTIPLE_BLOCK: {
+ if (s->is_SDHC) {
+ // arg is block offset
+ } else {
+ // arg is byte offset
+ if (arg & 511) fprintf(stderr, "offset %d is not multiple of 512 when reading\n", arg);
+ arg /= s->block_length;
+ }
+ result = bdrv_read(s->bs, arg, s->buffer, s->block_count);
+ new_status |= MMC_STAT_END_OF_DATA;
+ s->resp[0] = SET_R1_CURRENT_STATE(4) | R1_READY_FOR_DATA; // 2304
+ break;
+ }
+
+ case MMC_WRITE_BLOCK:
+ s->block_count = 1;
+ // fall through
+ case MMC_WRITE_MULTIPLE_BLOCK: {
+ if (s->is_SDHC) {
+ // arg is block offset
+ } else {
+ // arg is byte offset
+ if (arg & 511) fprintf(stderr, "offset %d is not multiple of 512 when writing\n", arg);
+ arg /= s->block_length;
+ }
+ // arg is byte offset
+ result = bdrv_write(s->bs, arg, s->buffer, s->block_count);
+// bdrv_flush(s->bs);
+ new_status |= MMC_STAT_END_OF_DATA;
+ s->resp[0] = SET_R1_CURRENT_STATE(4) | R1_READY_FOR_DATA; // 2304
+ break;
+ }
+
+ case MMC_STOP_TRANSMISSION:
+ s->resp[0] = SET_R1_CURRENT_STATE(5) | R1_READY_FOR_DATA; // 2816
+ break;
+
+ case MMC_SEND_STATUS:
+ s->resp[0] = SET_R1_CURRENT_STATE(4) | R1_READY_FOR_DATA; // 2304
+ break;
+ }
+
+ s->int_status |= new_status;
+
+ if ((s->int_status & s->int_enable)) {
+ goldfish_device_set_irq(&s->dev, 0, (s->int_status & s->int_enable));
+ }
+}
+
+static uint32_t goldfish_mmc_read(void *opaque, target_phys_addr_t offset)
+{
+ uint32_t ret;
+ struct goldfish_mmc_state *s = opaque;
+
+ offset -= s->dev.base;
+ switch(offset) {
+ case MMC_INT_STATUS:
+ // return current buffer status flags
+ return s->int_status & s->int_enable;
+ case MMC_RESP_0:
+ return s->resp[0];
+ case MMC_RESP_1:
+ return s->resp[1];
+ case MMC_RESP_2:
+ return s->resp[2];
+ case MMC_RESP_3:
+ return s->resp[3];
+ case MMC_STATE: {
+ ret = MMC_STATE_INSERTED;
+ if (bdrv_is_read_only(s->bs)) {
+ ret |= MMC_STATE_READ_ONLY;
+ }
+ return ret;
+ }
+ default:
+ cpu_abort(cpu_single_env, "goldfish_mmc_read: Bad offset %x\n", offset);
+ return 0;
+ }
+}
+
+static void goldfish_mmc_write(void *opaque, target_phys_addr_t offset, uint32_t val)
+{
+ struct goldfish_mmc_state *s = opaque;
+ int status, old_status;
+
+ offset -= s->dev.base;
+
+ switch(offset) {
+
+ case MMC_INT_STATUS:
+ status = s->int_status;
+ old_status = status;
+ status &= ~val;
+ s->int_status = status;
+ if(status != old_status) {
+ goldfish_device_set_irq(&s->dev, 0, status);
+ }
+ break;
+
+ case MMC_INT_ENABLE:
+ /* enable buffer interrupts */
+ s->int_enable = val;
+ s->int_status = 0;
+ goldfish_device_set_irq(&s->dev, 0, (s->int_status & s->int_enable));
+ break;
+ case MMC_SET_BUFFER:
+ /* save pointer to buffer 1 */
+ s->buffer = phys_ram_base + val;
+ break;
+ case MMC_CMD:
+ goldfish_mmc_do_command(s, val, s->arg);
+ break;
+ case MMC_ARG:
+ s->arg = val;
+ break;
+ case MMC_BLOCK_LENGTH:
+ s->block_length = val + 1;
+ break;
+ case MMC_BLOCK_COUNT:
+ s->block_count = val + 1;
+ break;
+
+ default:
+ cpu_abort (cpu_single_env, "goldfish_mmc_write: Bad offset %x\n", offset);
+ }
+}
+
+static CPUReadMemoryFunc *goldfish_mmc_readfn[] = {
+ goldfish_mmc_read,
+ goldfish_mmc_read,
+ goldfish_mmc_read
+};
+
+static CPUWriteMemoryFunc *goldfish_mmc_writefn[] = {
+ goldfish_mmc_write,
+ goldfish_mmc_write,
+ goldfish_mmc_write
+};
+
+void goldfish_mmc_init(uint32_t base, int id, BlockDriverState* bs)
+{
+ struct goldfish_mmc_state *s;
+
+ s = (struct goldfish_mmc_state *)qemu_mallocz(sizeof(*s));
+ s->dev.name = "goldfish_mmc";
+ s->dev.id = id;
+ s->dev.base = base;
+ s->dev.size = 0x1000;
+ s->dev.irq_count = 1;
+ s->bs = bs;
+
+ goldfish_device_add(&s->dev, goldfish_mmc_readfn, goldfish_mmc_writefn, s);
+
+ register_savevm( "goldfish_mmc", 0, GOLDFISH_MMC_SAVE_VERSION,
+ goldfish_mmc_save, goldfish_mmc_load, s);
+}
+