Browse Source
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2620 c046a42c-6fe2-441c-8c8c-71466251a162stable-0.10
10 changed files with 2084 additions and 8 deletions
@ -0,0 +1,445 @@ |
|||
/*
|
|||
* Arm PrimeCell PL181 MultiMedia Card Interface |
|||
* |
|||
* Copyright (c) 2007 CodeSourcery. |
|||
* Written by Paul Brook |
|||
* |
|||
* This code is licenced under the GPL. |
|||
*/ |
|||
|
|||
#include "vl.h" |
|||
#include "sd.h" |
|||
|
|||
//#define DEBUG_PL181 1
|
|||
|
|||
#ifdef DEBUG_PL181 |
|||
#define DPRINTF(fmt, args...) \ |
|||
do { printf("pl181: " fmt , ##args); } while (0) |
|||
#else |
|||
#define DPRINTF(fmt, args...) do {} while(0) |
|||
#endif |
|||
|
|||
#define PL181_FIFO_LEN 16 |
|||
|
|||
typedef struct { |
|||
struct sd_state_s *card; |
|||
uint32_t base; |
|||
uint32_t clock; |
|||
uint32_t power; |
|||
uint32_t cmdarg; |
|||
uint32_t cmd; |
|||
uint32_t datatimer; |
|||
uint32_t datalength; |
|||
uint32_t respcmd; |
|||
uint32_t response[4]; |
|||
uint32_t datactrl; |
|||
uint32_t datacnt; |
|||
uint32_t status; |
|||
uint32_t mask[2]; |
|||
uint32_t fifocnt; |
|||
int fifo_pos; |
|||
int fifo_len; |
|||
uint32_t fifo[PL181_FIFO_LEN]; |
|||
void *pic; |
|||
int irq[2]; |
|||
} pl181_state; |
|||
|
|||
#define PL181_CMD_INDEX 0x3f |
|||
#define PL181_CMD_RESPONSE (1 << 6) |
|||
#define PL181_CMD_LONGRESP (1 << 7) |
|||
#define PL181_CMD_INTERRUPT (1 << 8) |
|||
#define PL181_CMD_PENDING (1 << 9) |
|||
#define PL181_CMD_ENABLE (1 << 10) |
|||
|
|||
#define PL181_DATA_ENABLE (1 << 0) |
|||
#define PL181_DATA_DIRECTION (1 << 1) |
|||
#define PL181_DATA_MODE (1 << 2) |
|||
#define PL181_DATA_DMAENABLE (1 << 3) |
|||
|
|||
#define PL181_STATUS_CMDCRCFAIL (1 << 0) |
|||
#define PL181_STATUS_DATACRCFAIL (1 << 1) |
|||
#define PL181_STATUS_CMDTIMEOUT (1 << 2) |
|||
#define PL181_STATUS_DATATIMEOUT (1 << 3) |
|||
#define PL181_STATUS_TXUNDERRUN (1 << 4) |
|||
#define PL181_STATUS_RXOVERRUN (1 << 5) |
|||
#define PL181_STATUS_CMDRESPEND (1 << 6) |
|||
#define PL181_STATUS_CMDSENT (1 << 7) |
|||
#define PL181_STATUS_DATAEND (1 << 8) |
|||
#define PL181_STATUS_DATABLOCKEND (1 << 10) |
|||
#define PL181_STATUS_CMDACTIVE (1 << 11) |
|||
#define PL181_STATUS_TXACTIVE (1 << 12) |
|||
#define PL181_STATUS_RXACTIVE (1 << 13) |
|||
#define PL181_STATUS_TXFIFOHALFEMPTY (1 << 14) |
|||
#define PL181_STATUS_RXFIFOHALFFULL (1 << 15) |
|||
#define PL181_STATUS_TXFIFOFULL (1 << 16) |
|||
#define PL181_STATUS_RXFIFOFULL (1 << 17) |
|||
#define PL181_STATUS_TXFIFOEMPTY (1 << 18) |
|||
#define PL181_STATUS_RXFIFOEMPTY (1 << 19) |
|||
#define PL181_STATUS_TXDATAAVLBL (1 << 20) |
|||
#define PL181_STATUS_RXDATAAVLBL (1 << 21) |
|||
|
|||
#define PL181_STATUS_TX_FIFO (PL181_STATUS_TXACTIVE \ |
|||
|PL181_STATUS_TXFIFOHALFEMPTY \ |
|||
|PL181_STATUS_TXFIFOFULL \ |
|||
|PL181_STATUS_TXFIFOEMPTY \ |
|||
|PL181_STATUS_TXDATAAVLBL) |
|||
#define PL181_STATUS_RX_FIFO (PL181_STATUS_RXACTIVE \ |
|||
|PL181_STATUS_RXFIFOHALFFULL \ |
|||
|PL181_STATUS_RXFIFOFULL \ |
|||
|PL181_STATUS_RXFIFOEMPTY \ |
|||
|PL181_STATUS_RXDATAAVLBL) |
|||
|
|||
static const unsigned char pl181_id[] = |
|||
{ 0x81, 0x11, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; |
|||
|
|||
static void pl181_update(pl181_state *s) |
|||
{ |
|||
int i; |
|||
for (i = 0; i < 2; i++) { |
|||
pic_set_irq_new(s->pic, s->irq[i], (s->status & s->mask[i]) != 0); |
|||
} |
|||
} |
|||
|
|||
static void pl181_fifo_push(pl181_state *s, uint32_t value) |
|||
{ |
|||
int n; |
|||
|
|||
if (s->fifo_len == PL181_FIFO_LEN) { |
|||
fprintf(stderr, "pl181: FIFO overflow\n"); |
|||
return; |
|||
} |
|||
n = (s->fifo_pos + s->fifo_len) & (PL181_FIFO_LEN - 1); |
|||
s->fifo_len++; |
|||
s->fifo[n] = value; |
|||
DPRINTF("FIFO push %08x\n", (int)value); |
|||
} |
|||
|
|||
static uint32_t pl181_fifo_pop(pl181_state *s) |
|||
{ |
|||
uint32_t value; |
|||
|
|||
if (s->fifo_len == 0) { |
|||
fprintf(stderr, "pl181: FIFO underflow\n"); |
|||
return 0; |
|||
} |
|||
value = s->fifo[s->fifo_pos]; |
|||
s->fifo_len--; |
|||
s->fifo_pos = (s->fifo_pos + 1) & (PL181_FIFO_LEN - 1); |
|||
DPRINTF("FIFO pop %08x\n", (int)value); |
|||
return value; |
|||
} |
|||
|
|||
static void pl181_send_command(pl181_state *s) |
|||
{ |
|||
struct sd_request_s request; |
|||
uint8_t response[16]; |
|||
int rlen; |
|||
|
|||
request.cmd = s->cmd & PL181_CMD_INDEX; |
|||
request.arg = s->cmdarg; |
|||
DPRINTF("Command %d %08x\n", request.cmd, request.arg); |
|||
rlen = sd_do_command(s->card, &request, response); |
|||
if (rlen < 0) |
|||
goto error; |
|||
if (s->cmd & PL181_CMD_RESPONSE) { |
|||
#define RWORD(n) ((response[n] << 24) | (response[n + 1] << 16) \ |
|||
| (response[n + 2] << 8) | response[n + 3]) |
|||
if (rlen == 0 || (rlen == 4 && (s->cmd & PL181_CMD_LONGRESP))) |
|||
goto error; |
|||
if (rlen != 4 && rlen != 16) |
|||
goto error; |
|||
s->response[0] = RWORD(0); |
|||
if (rlen == 4) { |
|||
s->response[1] = s->response[2] = s->response[3] = 0; |
|||
} else { |
|||
s->response[1] = RWORD(4); |
|||
s->response[2] = RWORD(8); |
|||
s->response[3] = RWORD(12) & ~1; |
|||
} |
|||
DPRINTF("Response recieved\n"); |
|||
s->status |= PL181_STATUS_CMDRESPEND; |
|||
#undef RWORD |
|||
} else { |
|||
DPRINTF("Command sent\n"); |
|||
s->status |= PL181_STATUS_CMDSENT; |
|||
} |
|||
return; |
|||
|
|||
error: |
|||
DPRINTF("Timeout\n"); |
|||
s->status |= PL181_STATUS_CMDTIMEOUT; |
|||
} |
|||
|
|||
/* Transfer data between teh card and the FIFO. This is complicated by
|
|||
the FIFO holding 32-bit words and the card taking data in single byte |
|||
chunks. FIFO bytes are transferred in little-endian order. */ |
|||
|
|||
static void pl181_fifo_run(pl181_state *s) |
|||
{ |
|||
uint32_t bits; |
|||
uint32_t value; |
|||
int n; |
|||
int limit; |
|||
int is_read; |
|||
|
|||
is_read = (s->datactrl & PL181_DATA_DIRECTION) != 0; |
|||
if (s->datacnt != 0 && (!is_read || sd_data_ready(s->card))) { |
|||
limit = is_read ? PL181_FIFO_LEN : 0; |
|||
n = 0; |
|||
value = 0; |
|||
while (s->datacnt && s->fifo_len != limit) { |
|||
if (is_read) { |
|||
value |= (uint32_t)sd_read_data(s->card) << (n * 8); |
|||
n++; |
|||
if (n == 4) { |
|||
pl181_fifo_push(s, value); |
|||
value = 0; |
|||
n = 0; |
|||
} |
|||
} else { |
|||
if (n == 0) { |
|||
value = pl181_fifo_pop(s); |
|||
n = 4; |
|||
} |
|||
sd_write_data(s->card, value & 0xff); |
|||
value >>= 8; |
|||
n--; |
|||
} |
|||
s->datacnt--; |
|||
} |
|||
if (n && is_read) { |
|||
pl181_fifo_push(s, value); |
|||
} |
|||
} |
|||
s->status &= ~(PL181_STATUS_RX_FIFO | PL181_STATUS_TX_FIFO); |
|||
if (s->datacnt == 0) { |
|||
s->status |= PL181_STATUS_DATAEND; |
|||
/* HACK: */ |
|||
s->status |= PL181_STATUS_DATABLOCKEND; |
|||
DPRINTF("Transfer Complete\n"); |
|||
} |
|||
if (s->datacnt == 0 && s->fifocnt == 0) { |
|||
s->datactrl &= ~PL181_DATA_ENABLE; |
|||
DPRINTF("Data engine idle\n"); |
|||
} else { |
|||
/* Update FIFO bits. */ |
|||
bits = PL181_STATUS_TXACTIVE | PL181_STATUS_RXACTIVE; |
|||
if (s->fifo_len == 0) { |
|||
bits |= PL181_STATUS_TXFIFOEMPTY; |
|||
bits |= PL181_STATUS_RXFIFOEMPTY; |
|||
} else { |
|||
bits |= PL181_STATUS_TXDATAAVLBL; |
|||
bits |= PL181_STATUS_RXDATAAVLBL; |
|||
} |
|||
if (s->fifo_len == 16) { |
|||
bits |= PL181_STATUS_TXFIFOFULL; |
|||
bits |= PL181_STATUS_RXFIFOFULL; |
|||
} |
|||
if (s->fifo_len <= 8) { |
|||
bits |= PL181_STATUS_TXFIFOHALFEMPTY; |
|||
} |
|||
if (s->fifo_len >= 8) { |
|||
bits |= PL181_STATUS_RXFIFOHALFFULL; |
|||
} |
|||
if (s->datactrl & PL181_DATA_DIRECTION) { |
|||
bits &= PL181_STATUS_RX_FIFO; |
|||
} else { |
|||
bits &= PL181_STATUS_TX_FIFO; |
|||
} |
|||
s->status |= bits; |
|||
} |
|||
} |
|||
|
|||
static uint32_t pl181_read(void *opaque, target_phys_addr_t offset) |
|||
{ |
|||
pl181_state *s = (pl181_state *)opaque; |
|||
|
|||
offset -= s->base; |
|||
if (offset >= 0xfe0 && offset < 0x1000) { |
|||
return pl181_id[(offset - 0xfe0) >> 2]; |
|||
} |
|||
switch (offset) { |
|||
case 0x00: /* Power */ |
|||
return s->power; |
|||
case 0x04: /* Clock */ |
|||
return s->clock; |
|||
case 0x08: /* Argument */ |
|||
return s->cmdarg; |
|||
case 0x0c: /* Command */ |
|||
return s->cmd; |
|||
case 0x10: /* RespCmd */ |
|||
return s->respcmd; |
|||
case 0x14: /* Response0 */ |
|||
return s->response[0]; |
|||
case 0x18: /* Response1 */ |
|||
return s->response[1]; |
|||
case 0x1c: /* Response2 */ |
|||
return s->response[2]; |
|||
case 0x20: /* Response3 */ |
|||
return s->response[3]; |
|||
case 0x24: /* DataTimer */ |
|||
return s->datatimer; |
|||
case 0x28: /* DataLength */ |
|||
return s->datalength; |
|||
case 0x2c: /* DataCtrl */ |
|||
return s->datactrl; |
|||
case 0x30: /* DataCnt */ |
|||
return s->datacnt; |
|||
case 0x34: /* Status */ |
|||
return s->status; |
|||
case 0x3c: /* Mask0 */ |
|||
return s->mask[0]; |
|||
case 0x40: /* Mask1 */ |
|||
return s->mask[1]; |
|||
case 0x48: /* FifoCnt */ |
|||
return s->fifocnt; |
|||
case 0x80: case 0x84: case 0x88: case 0x8c: /* FifoData */ |
|||
case 0x90: case 0x94: case 0x98: case 0x9c: |
|||
case 0xa0: case 0xa4: case 0xa8: case 0xac: |
|||
case 0xb0: case 0xb4: case 0xb8: case 0xbc: |
|||
if (s->fifocnt == 0) { |
|||
fprintf(stderr, "pl181: Unexpected FIFO read\n"); |
|||
return 0; |
|||
} else { |
|||
uint32_t value; |
|||
s->fifocnt--; |
|||
value = pl181_fifo_pop(s); |
|||
pl181_fifo_run(s); |
|||
pl181_update(s); |
|||
return value; |
|||
} |
|||
default: |
|||
cpu_abort (cpu_single_env, "pl181_read: Bad offset %x\n", offset); |
|||
return 0; |
|||
} |
|||
} |
|||
|
|||
static void pl181_write(void *opaque, target_phys_addr_t offset, |
|||
uint32_t value) |
|||
{ |
|||
pl181_state *s = (pl181_state *)opaque; |
|||
|
|||
offset -= s->base; |
|||
switch (offset) { |
|||
case 0x00: /* Power */ |
|||
s->power = value & 0xff; |
|||
break; |
|||
case 0x04: /* Clock */ |
|||
s->clock = value & 0xff; |
|||
break; |
|||
case 0x08: /* Argument */ |
|||
s->cmdarg = value; |
|||
break; |
|||
case 0x0c: /* Command */ |
|||
s->cmd = value; |
|||
if (s->cmd & PL181_CMD_ENABLE) { |
|||
if (s->cmd & PL181_CMD_INTERRUPT) { |
|||
fprintf(stderr, "pl181: Interrupt mode not implemented\n"); |
|||
abort(); |
|||
} if (s->cmd & PL181_CMD_PENDING) { |
|||
fprintf(stderr, "pl181: Pending commands not implemented\n"); |
|||
abort(); |
|||
} else { |
|||
pl181_send_command(s); |
|||
pl181_fifo_run(s); |
|||
} |
|||
/* The command has completed one way or the other. */ |
|||
s->cmd &= ~PL181_CMD_ENABLE; |
|||
} |
|||
break; |
|||
case 0x24: /* DataTimer */ |
|||
s->datatimer = value; |
|||
break; |
|||
case 0x28: /* DataLength */ |
|||
s->datalength = value & 0xffff; |
|||
break; |
|||
case 0x2c: /* DataCtrl */ |
|||
s->datactrl = value & 0xff; |
|||
if (value & PL181_DATA_ENABLE) { |
|||
s->datacnt = s->datalength; |
|||
s->fifocnt = (s->datalength + 3) >> 2; |
|||
pl181_fifo_run(s); |
|||
} |
|||
break; |
|||
case 0x38: /* Clear */ |
|||
s->status &= ~(value & 0x7ff); |
|||
break; |
|||
case 0x3c: /* Mask0 */ |
|||
s->mask[0] = value; |
|||
break; |
|||
case 0x40: /* Mask1 */ |
|||
s->mask[1] = value; |
|||
break; |
|||
case 0x80: case 0x84: case 0x88: case 0x8c: /* FifoData */ |
|||
case 0x90: case 0x94: case 0x98: case 0x9c: |
|||
case 0xa0: case 0xa4: case 0xa8: case 0xac: |
|||
case 0xb0: case 0xb4: case 0xb8: case 0xbc: |
|||
if (s->fifocnt == 0) { |
|||
fprintf(stderr, "pl181: Unexpected FIFO write\n"); |
|||
} else { |
|||
s->fifocnt--; |
|||
pl181_fifo_push(s, value); |
|||
pl181_fifo_run(s); |
|||
} |
|||
break; |
|||
default: |
|||
cpu_abort (cpu_single_env, "pl181_write: Bad offset %x\n", offset); |
|||
} |
|||
pl181_update(s); |
|||
} |
|||
|
|||
static CPUReadMemoryFunc *pl181_readfn[] = { |
|||
pl181_read, |
|||
pl181_read, |
|||
pl181_read |
|||
}; |
|||
|
|||
static CPUWriteMemoryFunc *pl181_writefn[] = { |
|||
pl181_write, |
|||
pl181_write, |
|||
pl181_write |
|||
}; |
|||
|
|||
static void pl181_reset(void *opaque) |
|||
{ |
|||
pl181_state *s = (pl181_state *)opaque; |
|||
|
|||
s->power = 0; |
|||
s->cmdarg = 0; |
|||
s->cmd = 0; |
|||
s->datatimer = 0; |
|||
s->datalength = 0; |
|||
s->respcmd = 0; |
|||
s->response[0] = 0; |
|||
s->response[1] = 0; |
|||
s->response[2] = 0; |
|||
s->response[3] = 0; |
|||
s->datatimer = 0; |
|||
s->datalength = 0; |
|||
s->datactrl = 0; |
|||
s->datacnt = 0; |
|||
s->status = 0; |
|||
s->mask[0] = 0; |
|||
s->mask[1] = 0; |
|||
s->fifocnt = 0; |
|||
} |
|||
|
|||
void pl181_init(uint32_t base, BlockDriverState *bd, |
|||
void *pic, int irq0, int irq1) |
|||
{ |
|||
int iomemtype; |
|||
pl181_state *s; |
|||
|
|||
s = (pl181_state *)qemu_mallocz(sizeof(pl181_state)); |
|||
iomemtype = cpu_register_io_memory(0, pl181_readfn, |
|||
pl181_writefn, s); |
|||
cpu_register_physical_memory(base, 0x00000fff, iomemtype); |
|||
s->base = base; |
|||
s->card = sd_init(bd); |
|||
s->pic = pic; |
|||
s->irq[0] = irq0; |
|||
s->irq[1] = irq1; |
|||
qemu_register_reset(pl181_reset, s); |
|||
pl181_reset(s); |
|||
/* ??? Save/restore. */ |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,82 @@ |
|||
/*
|
|||
* SD Memory Card emulation. Mostly correct for MMC too. |
|||
* |
|||
* Copyright (c) 2006 Andrzej Zaborowski <balrog@zabor.org> |
|||
* |
|||
* Redistribution and use in source and binary forms, with or without |
|||
* modification, are permitted provided that the following conditions |
|||
* are met: |
|||
* |
|||
* 1. Redistributions of source code must retain the above copyright |
|||
* notice, this list of conditions and the following disclaimer. |
|||
* 2. Redistributions in binary form must reproduce the above copyright |
|||
* notice, this list of conditions and the following disclaimer in |
|||
* the documentation and/or other materials provided with the |
|||
* distribution. |
|||
* |
|||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' |
|||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
|||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A |
|||
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR |
|||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
|||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
|||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
|||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
|||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|||
*/ |
|||
#ifndef __hw_sd_h |
|||
#define __hw_sd_h 1 |
|||
|
|||
#include <vl.h> |
|||
|
|||
#define OUT_OF_RANGE (1 << 31) |
|||
#define ADDRESS_ERROR (1 << 30) |
|||
#define BLOCK_LEN_ERROR (1 << 29) |
|||
#define ERASE_SEQ_ERROR (1 << 28) |
|||
#define ERASE_PARAM (1 << 27) |
|||
#define WP_VIOLATION (1 << 26) |
|||
#define CARD_IS_LOCKED (1 << 25) |
|||
#define LOCK_UNLOCK_FAILED (1 << 24) |
|||
#define COM_CRC_ERROR (1 << 23) |
|||
#define ILLEGAL_COMMAND (1 << 22) |
|||
#define CARD_ECC_FAILED (1 << 21) |
|||
#define CC_ERROR (1 << 20) |
|||
#define SD_ERROR (1 << 19) |
|||
#define CID_CSD_OVERWRITE (1 << 16) |
|||
#define WP_ERASE_SKIP (1 << 15) |
|||
#define CARD_ECC_DISABLED (1 << 14) |
|||
#define ERASE_RESET (1 << 13) |
|||
#define CURRENT_STATE (7 << 9) |
|||
#define READY_FOR_DATA (1 << 8) |
|||
#define APP_CMD (1 << 5) |
|||
#define AKE_SEQ_ERROR (1 << 3) |
|||
|
|||
typedef enum { |
|||
sd_none = -1, |
|||
sd_bc = 0, /* broadcast -- no response */ |
|||
sd_bcr, /* broadcast with response */ |
|||
sd_ac, /* addressed -- no data transfer */ |
|||
sd_adtc, /* addressed with data transfer */ |
|||
} sd_cmd_type_t; |
|||
|
|||
struct sd_request_s { |
|||
uint8_t cmd; |
|||
uint32_t arg; |
|||
uint8_t crc; |
|||
}; |
|||
|
|||
typedef struct SDState SDState; |
|||
|
|||
SDState *sd_init(BlockDriverState *bs); |
|||
int sd_do_command(SDState *sd, struct sd_request_s *req, |
|||
uint8_t *response); |
|||
void sd_write_data(SDState *sd, uint8_t value); |
|||
uint8_t sd_read_data(SDState *sd); |
|||
void sd_set_cb(SDState *sd, void *opaque, |
|||
void (*readonly_cb)(void *, int), |
|||
void (*inserted_cb)(void *, int)); |
|||
int sd_data_ready(SDState *sd); |
|||
|
|||
#endif /* __hw_sd_h */ |
|||
Loading…
Reference in new issue