|  | /* | 
|  | *  QEMU model of the Milkymist SD Card Controller. | 
|  | * | 
|  | *  Copyright (c) 2010 Michael Walle <michael@walle.cc> | 
|  | * | 
|  | * This library is free software; you can redistribute it and/or | 
|  | * modify it under the terms of the GNU Lesser General Public | 
|  | * License as published by the Free Software Foundation; either | 
|  | * version 2 of the License, or (at your option) any later version. | 
|  | * | 
|  | * This library 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 | 
|  | * Lesser General Public License for more details. | 
|  | * | 
|  | * You should have received a copy of the GNU Lesser General Public | 
|  | * License along with this library; if not, see <http://www.gnu.org/licenses/>. | 
|  | * | 
|  | * | 
|  | * Specification available at: | 
|  | *   http://www.milkymist.org/socdoc/memcard.pdf | 
|  | */ | 
|  |  | 
|  | #include "hw.h" | 
|  | #include "sysbus.h" | 
|  | #include "sysemu.h" | 
|  | #include "trace.h" | 
|  | #include "qemu-error.h" | 
|  | #include "blockdev.h" | 
|  | #include "sd.h" | 
|  |  | 
|  | enum { | 
|  | ENABLE_CMD_TX   = (1<<0), | 
|  | ENABLE_CMD_RX   = (1<<1), | 
|  | ENABLE_DAT_TX   = (1<<2), | 
|  | ENABLE_DAT_RX   = (1<<3), | 
|  | }; | 
|  |  | 
|  | enum { | 
|  | PENDING_CMD_TX   = (1<<0), | 
|  | PENDING_CMD_RX   = (1<<1), | 
|  | PENDING_DAT_TX   = (1<<2), | 
|  | PENDING_DAT_RX   = (1<<3), | 
|  | }; | 
|  |  | 
|  | enum { | 
|  | START_CMD_TX    = (1<<0), | 
|  | START_DAT_RX    = (1<<1), | 
|  | }; | 
|  |  | 
|  | enum { | 
|  | R_CLK2XDIV = 0, | 
|  | R_ENABLE, | 
|  | R_PENDING, | 
|  | R_START, | 
|  | R_CMD, | 
|  | R_DAT, | 
|  | R_MAX | 
|  | }; | 
|  |  | 
|  | struct MilkymistMemcardState { | 
|  | SysBusDevice busdev; | 
|  | SDState *card; | 
|  |  | 
|  | int command_write_ptr; | 
|  | int response_read_ptr; | 
|  | int response_len; | 
|  | int ignore_next_cmd; | 
|  | int enabled; | 
|  | uint8_t command[6]; | 
|  | uint8_t response[17]; | 
|  | uint32_t regs[R_MAX]; | 
|  | }; | 
|  | typedef struct MilkymistMemcardState MilkymistMemcardState; | 
|  |  | 
|  | static void update_pending_bits(MilkymistMemcardState *s) | 
|  | { | 
|  | /* transmits are instantaneous, thus tx pending bits are never set */ | 
|  | s->regs[R_PENDING] = 0; | 
|  | /* if rx is enabled the corresponding pending bits are always set */ | 
|  | if (s->regs[R_ENABLE] & ENABLE_CMD_RX) { | 
|  | s->regs[R_PENDING] |= PENDING_CMD_RX; | 
|  | } | 
|  | if (s->regs[R_ENABLE] & ENABLE_DAT_RX) { | 
|  | s->regs[R_PENDING] |= PENDING_DAT_RX; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void memcard_sd_command(MilkymistMemcardState *s) | 
|  | { | 
|  | SDRequest req; | 
|  |  | 
|  | req.cmd = s->command[0] & 0x3f; | 
|  | req.arg = (s->command[1] << 24) | (s->command[2] << 16) | 
|  | | (s->command[3] << 8) | s->command[4]; | 
|  | req.crc = s->command[5]; | 
|  |  | 
|  | s->response[0] = req.cmd; | 
|  | s->response_len = sd_do_command(s->card, &req, s->response+1); | 
|  | s->response_read_ptr = 0; | 
|  |  | 
|  | if (s->response_len == 16) { | 
|  | /* R2 response */ | 
|  | s->response[0] = 0x3f; | 
|  | s->response_len += 1; | 
|  | } else if (s->response_len == 4) { | 
|  | /* no crc calculation, insert dummy byte */ | 
|  | s->response[5] = 0; | 
|  | s->response_len += 2; | 
|  | } | 
|  |  | 
|  | if (req.cmd == 0) { | 
|  | /* next write is a dummy byte to clock the initialization of the sd | 
|  | * card */ | 
|  | s->ignore_next_cmd = 1; | 
|  | } | 
|  | } | 
|  |  | 
|  | static uint32_t memcard_read(void *opaque, target_phys_addr_t addr) | 
|  | { | 
|  | MilkymistMemcardState *s = opaque; | 
|  | uint32_t r = 0; | 
|  |  | 
|  | addr >>= 2; | 
|  | switch (addr) { | 
|  | case R_CMD: | 
|  | if (!s->enabled) { | 
|  | r = 0xff; | 
|  | } else { | 
|  | r = s->response[s->response_read_ptr++]; | 
|  | if (s->response_read_ptr > s->response_len) { | 
|  | error_report("milkymist_memcard: " | 
|  | "read more cmd bytes than available. Clipping."); | 
|  | s->response_read_ptr = 0; | 
|  | } | 
|  | } | 
|  | break; | 
|  | case R_DAT: | 
|  | if (!s->enabled) { | 
|  | r = 0xffffffff; | 
|  | } else { | 
|  | r = 0; | 
|  | r |= sd_read_data(s->card) << 24; | 
|  | r |= sd_read_data(s->card) << 16; | 
|  | r |= sd_read_data(s->card) << 8; | 
|  | r |= sd_read_data(s->card); | 
|  | } | 
|  | break; | 
|  | case R_CLK2XDIV: | 
|  | case R_ENABLE: | 
|  | case R_PENDING: | 
|  | case R_START: | 
|  | r = s->regs[addr]; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | error_report("milkymist_memcard: read access to unknown register 0x" | 
|  | TARGET_FMT_plx, addr << 2); | 
|  | break; | 
|  | } | 
|  |  | 
|  | trace_milkymist_memcard_memory_read(addr << 2, r); | 
|  |  | 
|  | return r; | 
|  | } | 
|  |  | 
|  | static void memcard_write(void *opaque, target_phys_addr_t addr, uint32_t value) | 
|  | { | 
|  | MilkymistMemcardState *s = opaque; | 
|  |  | 
|  | trace_milkymist_memcard_memory_write(addr, value); | 
|  |  | 
|  | addr >>= 2; | 
|  | switch (addr) { | 
|  | case R_PENDING: | 
|  | /* clear rx pending bits */ | 
|  | s->regs[R_PENDING] &= ~(value & (PENDING_CMD_RX | PENDING_DAT_RX)); | 
|  | update_pending_bits(s); | 
|  | break; | 
|  | case R_CMD: | 
|  | if (!s->enabled) { | 
|  | break; | 
|  | } | 
|  | if (s->ignore_next_cmd) { | 
|  | s->ignore_next_cmd = 0; | 
|  | break; | 
|  | } | 
|  | s->command[s->command_write_ptr] = value & 0xff; | 
|  | s->command_write_ptr = (s->command_write_ptr + 1) % 6; | 
|  | if (s->command_write_ptr == 0) { | 
|  | memcard_sd_command(s); | 
|  | } | 
|  | break; | 
|  | case R_DAT: | 
|  | if (!s->enabled) { | 
|  | break; | 
|  | } | 
|  | sd_write_data(s->card, (value >> 24) & 0xff); | 
|  | sd_write_data(s->card, (value >> 16) & 0xff); | 
|  | sd_write_data(s->card, (value >> 8) & 0xff); | 
|  | sd_write_data(s->card, value & 0xff); | 
|  | break; | 
|  | case R_ENABLE: | 
|  | s->regs[addr] = value; | 
|  | update_pending_bits(s); | 
|  | break; | 
|  | case R_CLK2XDIV: | 
|  | case R_START: | 
|  | s->regs[addr] = value; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | error_report("milkymist_memcard: write access to unknown register 0x" | 
|  | TARGET_FMT_plx, addr << 2); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | static CPUReadMemoryFunc * const memcard_read_fn[] = { | 
|  | NULL, | 
|  | NULL, | 
|  | &memcard_read, | 
|  | }; | 
|  |  | 
|  | static CPUWriteMemoryFunc * const memcard_write_fn[] = { | 
|  | NULL, | 
|  | NULL, | 
|  | &memcard_write, | 
|  | }; | 
|  |  | 
|  | static void milkymist_memcard_reset(DeviceState *d) | 
|  | { | 
|  | MilkymistMemcardState *s = | 
|  | container_of(d, MilkymistMemcardState, busdev.qdev); | 
|  | int i; | 
|  |  | 
|  | s->command_write_ptr = 0; | 
|  | s->response_read_ptr = 0; | 
|  | s->response_len = 0; | 
|  |  | 
|  | for (i = 0; i < R_MAX; i++) { | 
|  | s->regs[i] = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int milkymist_memcard_init(SysBusDevice *dev) | 
|  | { | 
|  | MilkymistMemcardState *s = FROM_SYSBUS(typeof(*s), dev); | 
|  | DriveInfo *dinfo; | 
|  | int memcard_regs; | 
|  |  | 
|  | dinfo = drive_get_next(IF_SD); | 
|  | s->card = sd_init(dinfo ? dinfo->bdrv : NULL, 0); | 
|  | s->enabled = dinfo ? bdrv_is_inserted(dinfo->bdrv) : 0; | 
|  |  | 
|  | memcard_regs = cpu_register_io_memory(memcard_read_fn, memcard_write_fn, s, | 
|  | DEVICE_NATIVE_ENDIAN); | 
|  | sysbus_init_mmio(dev, R_MAX * 4, memcard_regs); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const VMStateDescription vmstate_milkymist_memcard = { | 
|  | .name = "milkymist-memcard", | 
|  | .version_id = 1, | 
|  | .minimum_version_id = 1, | 
|  | .minimum_version_id_old = 1, | 
|  | .fields      = (VMStateField[]) { | 
|  | VMSTATE_INT32(command_write_ptr, MilkymistMemcardState), | 
|  | VMSTATE_INT32(response_read_ptr, MilkymistMemcardState), | 
|  | VMSTATE_INT32(response_len, MilkymistMemcardState), | 
|  | VMSTATE_INT32(ignore_next_cmd, MilkymistMemcardState), | 
|  | VMSTATE_INT32(enabled, MilkymistMemcardState), | 
|  | VMSTATE_UINT8_ARRAY(command, MilkymistMemcardState, 6), | 
|  | VMSTATE_UINT8_ARRAY(response, MilkymistMemcardState, 17), | 
|  | VMSTATE_UINT32_ARRAY(regs, MilkymistMemcardState, R_MAX), | 
|  | VMSTATE_END_OF_LIST() | 
|  | } | 
|  | }; | 
|  |  | 
|  | static SysBusDeviceInfo milkymist_memcard_info = { | 
|  | .init = milkymist_memcard_init, | 
|  | .qdev.name  = "milkymist-memcard", | 
|  | .qdev.size  = sizeof(MilkymistMemcardState), | 
|  | .qdev.vmsd  = &vmstate_milkymist_memcard, | 
|  | .qdev.reset = milkymist_memcard_reset, | 
|  | }; | 
|  |  | 
|  | static void milkymist_memcard_register(void) | 
|  | { | 
|  | sysbus_register_withprop(&milkymist_memcard_info); | 
|  | } | 
|  |  | 
|  | device_init(milkymist_memcard_register) |