From patchwork Tue Apr 3 06:17:29 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Peter A. G. Crosthwaite" X-Patchwork-Id: 150327 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id A8DE7B6FE0 for ; Tue, 3 Apr 2012 16:17:50 +1000 (EST) Received: from localhost ([::1]:57834 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SEx3s-0002CN-HT for incoming@patchwork.ozlabs.org; Tue, 03 Apr 2012 02:17:48 -0400 Received: from eggs.gnu.org ([208.118.235.92]:49458) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SEx3f-0001xs-LJ for qemu-devel@nongnu.org; Tue, 03 Apr 2012 02:17:39 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1SEx3c-0000mY-0M for qemu-devel@nongnu.org; Tue, 03 Apr 2012 02:17:35 -0400 Received: from mail-pz0-f46.google.com ([209.85.210.46]:39340) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SEx3b-0000mK-K9 for qemu-devel@nongnu.org; Tue, 03 Apr 2012 02:17:31 -0400 Received: by dadz9 with SMTP id z9so3866534dad.33 for ; Mon, 02 Apr 2012 23:17:29 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references :in-reply-to:references:x-gm-message-state; bh=nRHM4LbhNWIoqWxHnwJisa6pXPF23dcPgTj3VodAnc8=; b=k5CEDbt+IrRFUMNBCAWDqXklT0MPenVFaMadXlhO5rFYp1z3ZYz7u58EhGaUxWQF2z rCJ2yVP+71k1+OrHjjyrc0CUheDX8XtTZQioe4rsHAc/g/9KFqftwKvrjcwrS/JMM+gI Kbeddlt6hPbzk6m+ytrPpbBtpviXXjl4r6klt3fb9d0Is9ituyfCDokqs0U8+XPBSk7U HIPe631JvG6y+e/H1ycG4gZz4QxYtb8exmLy+qa0XdQHgykYj8+I6E2Y+Z3LtbINLBpD 0DgqONp4oewxkVB8bMVZvaEFDVozSGZd5z47+ol5gjCv0IKO2715lzXbrfA5JIWsQAbH 2Hdg== Received: by 10.68.228.129 with SMTP id si1mr4690426pbc.55.1333433848997; Mon, 02 Apr 2012 23:17:28 -0700 (PDT) Received: from localhost ([124.148.20.9]) by mx.google.com with ESMTPS id f6sm15632727pbt.4.2012.04.02.23.17.23 (version=TLSv1/SSLv3 cipher=OTHER); Mon, 02 Apr 2012 23:17:28 -0700 (PDT) From: "Peter A. G. Crosthwaite" To: peter.crosthwaite@petalogix.com, peter.maydell@linaro.org, qemu-devel@nongnu.org, edgar.iglesias@gmail.com, i.mitsyanko@samsung.com Date: Tue, 3 Apr 2012 16:17:29 +1000 Message-Id: <93cf7bb1ab29e6c79ecd93f32b50c52b816cd10d.1333433027.git.peter.crosthwaite@petalogix.com> X-Mailer: git-send-email 1.7.3.2 In-Reply-To: References: In-Reply-To: References: X-Gm-Message-State: ALoCoQmdNJ+2BppKhiQMbV/r0mgzsOw7plwk0erw9NbQw0aE3ityliYicY4YDAT5oMPgyeTOhQfC X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 209.85.210.46 Cc: kwolf@redhat.com, vpalatin@chromium.org, d.solodkiy@samsung.com, duyl@xilinx.com, linnj@xilinx.com, john.williams@petalogix.com, paul@codesourcery.com Subject: [Qemu-devel] [PATCH v2 1/2] SDHCI: inital version X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Device model for standard SD Host Controller Interface (SDHCI). Signed-off-by: Peter A. G. Crosthwaite --- changed from v1: Made undefined op behaviour consisistent. fixed compile warning. Makefile.target | 1 + hw/sdhci.c | 736 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 737 insertions(+), 0 deletions(-) create mode 100644 hw/sdhci.c diff --git a/Makefile.target b/Makefile.target index 9b0cf74..c118dc6 100644 --- a/Makefile.target +++ b/Makefile.target @@ -363,6 +363,7 @@ obj-arm-y += versatile_pci.o obj-arm-y += cadence_uart.o obj-arm-y += cadence_ttc.o obj-arm-y += cadence_gem.o +obj-arm-y += sdhci.o obj-arm-y += xilinx_zynq.o zynq_slcr.o obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o obj-arm-y += exynos4210_gic.o exynos4210_combiner.o exynos4210.o diff --git a/hw/sdhci.c b/hw/sdhci.c new file mode 100644 index 0000000..b387013 --- /dev/null +++ b/hw/sdhci.c @@ -0,0 +1,736 @@ +/* + * Copyright 2011 Google Inc. + * Copyright (C) 2012 Peter A.G. Crosthwaite + * Copyright (C) 2012 PetaLogix Pty Ltd. + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * + * SDHCI (SD Host Controler Interface) emulation + */ + +#include "blockdev.h" +#include "sysbus.h" +#include "sd.h" + +#ifdef SDHCI_ERR_DEBUG +#define DB_PRINT(...) do { \ + fprintf(stderr, ": %s: ", __func__); \ + fprintf(stderr, ## __VA_ARGS__); \ + } while (0); +#else + #define DB_PRINT(...) +#endif + + +/* Controller registers */ + +#define SDHCI_DMA_ADDRESS 0x00 + +#define SDHCI_BLOCK_SIZE 0x04 + +#define SDHCI_BLOCK_COUNT 0x06 + +#define SDHCI_ARGUMENT 0x08 + +#define SDHCI_TRANSFER_MODE 0x0C +#define SDHCI_TRNS_DMA 0x01 +#define SDHCI_TRNS_BLK_CNT_EN 0x02 +#define SDHCI_TRNS_ACMD12 0x04 +#define SDHCI_TRNS_READ 0x10 +#define SDHCI_TRNS_MULTI 0x20 + +#define SDHCI_COMMAND 0x0E +#define SDHCI_CMD_RESP_MASK 0x03 +#define SDHCI_CMD_CRC 0x08 +#define SDHCI_CMD_INDEX 0x10 +#define SDHCI_CMD_DATA 0x20 + +#define SDHCI_CMD_RESP_NONE 0x00 +#define SDHCI_CMD_RESP_LONG 0x01 +#define SDHCI_CMD_RESP_SHORT 0x02 +#define SDHCI_CMD_RESP_SHORT_BUSY 0x03 + +#define SDHCI_RESPONSE 0x10 + +#define SDHCI_BUFFER 0x20 + +#define SDHCI_PRESENT_STATE 0x24 +#define SDHCI_CMD_INHIBIT 0x00000001 +#define SDHCI_DATA_INHIBIT 0x00000002 +#define SDHCI_DOING_WRITE 0x00000100 +#define SDHCI_DOING_READ 0x00000200 +#define SDHCI_SPACE_AVAILABLE 0x00000400 +#define SDHCI_DATA_AVAILABLE 0x00000800 +#define SDHCI_CARD_PRESENT 0x00010000 +#define SDHCI_WRITE_PROTECT 0x00080000 + +#define SDHCI_HOST_CONTROL 0x28 +#define SDHCI_CTRL_LED 0x01 +#define SDHCI_CTRL_4BITBUS 0x02 +#define SDHCI_CTRL_HISPD 0x04 +#define SDHCI_CTRL_DMA_MASK 0x18 +#define SDHCI_CTRL_SDMA 0x00 +#define SDHCI_CTRL_ADMA1 0x08 +#define SDHCI_CTRL_ADMA32 0x10 +#define SDHCI_CTRL_ADMA64 0x18 +#define SDHCI_CTRL_8BITBUS 0x20 + +#define SDHCI_POWER_CONTROL 0x29 +#define SDHCI_POWER_ON 0x01 +#define SDHCI_POWER_180 0x0A +#define SDHCI_POWER_300 0x0C +#define SDHCI_POWER_330 0x0E + +#define SDHCI_BLOCK_GAP_CONTROL 0x2A + +#define SDHCI_WAKE_UP_CONTROL 0x2B +#define SDHCI_WAKE_ON_INT 0x01 +#define SDHCI_WAKE_ON_INSERT 0x02 +#define SDHCI_WAKE_ON_REMOVE 0x04 + +#define SDHCI_CLOCK_CONTROL 0x2C +#define SDHCI_DIVIDER_SHIFT 8 +#define SDHCI_DIVIDER_HI_SHIFT 6 +#define SDHCI_DIV_MASK 0xFF +#define SDHCI_DIV_MASK_LEN 8 +#define SDHCI_DIV_HI_MASK 0x300 +#define SDHCI_CLOCK_CARD_EN 0x0004 +#define SDHCI_CLOCK_INT_STABLE 0x0002 +#define SDHCI_CLOCK_INT_EN 0x0001 + +#define SDHCI_TIMEOUT_CONTROL 0x2E + +#define SDHCI_SOFTWARE_RESET 0x2F +#define SDHCI_RESET_ALL 0x01 +#define SDHCI_RESET_CMD 0x02 +#define SDHCI_RESET_DATA 0x04 + +#define SDHCI_INT_STATUS 0x30 +#define SDHCI_INT_ENABLE 0x34 +#define SDHCI_SIGNAL_ENABLE 0x38 +#define SDHCI_INT_RESPONSE 0x00000001 +#define SDHCI_INT_DATA_END 0x00000002 +#define SDHCI_INT_DMA_END 0x00000008 +#define SDHCI_INT_SPACE_AVAIL 0x00000010 +#define SDHCI_INT_DATA_AVAIL 0x00000020 +#define SDHCI_INT_CARD_INSERT 0x00000040 +#define SDHCI_INT_CARD_REMOVE 0x00000080 +#define SDHCI_INT_CARD_INT 0x00000100 +#define SDHCI_INT_ERROR 0x00008000 +#define SDHCI_INT_TIMEOUT 0x00010000 +#define SDHCI_INT_CRC 0x00020000 +#define SDHCI_INT_END_BIT 0x00040000 +#define SDHCI_INT_INDEX 0x00080000 +#define SDHCI_INT_DATA_TIMEOUT 0x00100000 +#define SDHCI_INT_DATA_CRC 0x00200000 +#define SDHCI_INT_DATA_END_BIT 0x00400000 +#define SDHCI_INT_BUS_POWER 0x00800000 +#define SDHCI_INT_ACMD12ERR 0x01000000 +#define SDHCI_INT_ADMA_ERROR 0x02000000 + +#define SDHCI_INT_NORMAL_MASK 0x00007FFF +#define SDHCI_INT_ERROR_MASK 0xFFFF8000 + +#define SDHCI_ACMD12_ERR 0x3C + +/* 3E-3F reserved */ + +#define SDHCI_CAPABILITIES 0x40 +#define SDHCI_TIMEOUT_CLK_MASK 0x0000003F +#define SDHCI_TIMEOUT_CLK_SHIFT 0 +#define SDHCI_TIMEOUT_CLK_UNIT 0x00000080 +#define SDHCI_CLOCK_BASE_MASK 0x00003F00 +#define SDHCI_CLOCK_V3_BASE_MASK 0x0000FF00 +#define SDHCI_CLOCK_BASE_SHIFT 8 +#define SDHCI_MAX_BLOCK_MASK 0x00030000 +#define SDHCI_MAX_BLOCK_SHIFT 16 +#define SDHCI_CAN_DO_8BIT 0x00040000 +#define SDHCI_CAN_DO_ADMA2 0x00080000 +#define SDHCI_CAN_DO_ADMA1 0x00100000 +#define SDHCI_CAN_DO_HISPD 0x00200000 +#define SDHCI_CAN_DO_SDMA 0x00400000 +#define SDHCI_CAN_VDD_330 0x01000000 +#define SDHCI_CAN_VDD_300 0x02000000 +#define SDHCI_CAN_VDD_180 0x04000000 +#define SDHCI_CAN_64BIT 0x10000000 +#define SDHCI_SLOT_TYPE_EMBED 0x40000000 + +#define SDHCI_CAPABILITIES_1 0x44 + +/* 44-47 reserved for more caps */ + +#define SDHCI_MAX_CURRENT 0x48 + +/* 4C-4F reserved for more max current */ + +#define SDHCI_SET_ACMD12_ERROR 0x50 +#define SDHCI_SET_INT_ERROR 0x52 + +#define SDHCI_ADMA_ERROR 0x54 + +/* 55-57 reserved */ + +#define SDHCI_ADMA_ADDRESS 0x58 + +/* 60-FB reserved */ + +#define SDHCI_SLOT_INT_STATUS 0xFC + +#define SDHCI_HOST_VERSION 0xFE +#define SDHCI_VENDOR_VER_MASK 0xFF00 +#define SDHCI_VENDOR_VER_SHIFT 8 +#define SDHCI_SPEC_VER_MASK 0x00FF +#define SDHCI_SPEC_VER_SHIFT 0 +#define SDHCI_SPEC_100 0 +#define SDHCI_SPEC_200 1 +#define SDHCI_SPEC_300 2 + +/* ADMA descriptor flags */ +#define ADMA_VALID (1<<0) +#define ADMA_END (1<<1) +#define ADMA_INT (1<<2) + +#define ADMA_OP_MASK (3<<4) +#define ADMA_OP_NOP (0<<4) +#define ADMA_OP_SET (1<<4) +#define ADMA_OP_TRAN (2<<4) +#define ADMA_OP_LINK (3<<4) + +/* re-write a part of a variable using a mask + * to emulate the "byte-enable" behaviour in hardware + */ +#define MASKED_WRITE(var, val, mask) do {\ + var = (var & ~(mask)) | ((val) & (mask));\ + } while (0) + +typedef struct { + SysBusDevice busdev; + MemoryRegion iomem; + BlockDriverState *bs; + uint32_t sdma_address; + uint16_t block_size; + uint16_t block_count; + uint32_t arg; + uint16_t transfer_mode; + uint32_t response[4]; + uint32_t clock; + uint32_t host_control; + uint32_t present_state; + uint32_t int_enable; + uint32_t int_status; + uint32_t adma_address; + uint8_t adma_error; + SDState *sd; + qemu_irq irq; + uint32_t pio_btt; +} SDHCIState; + +static void sdhci_reset(DeviceState *d) +{ + SDHCIState *s = container_of(d, SDHCIState, busdev.qdev); + + if (!s->bs) { + DriveInfo *inf = drive_get_next(IF_SD); + s->bs = inf ? inf->bdrv : NULL; + } + if (!s->sd && s->bs) { + s->sd = sd_init(s->bs, 0); + } + + s->sdma_address = 0; + s->block_size = 0; + s->block_count = 0; + s->arg = 0; + s->transfer_mode = 0; + s->clock = SDHCI_CLOCK_INT_STABLE; + s->int_enable = 0; + s->int_status = 0; + s->adma_address = 0; + s->adma_error = 0; +} + +static void sdhci_set_irq(SDHCIState *s) +{ + qemu_set_irq(s->irq, !!(s->int_status & s->int_enable)); +} + +static void sdhci_end_of_transfer(SDHCIState *s) +{ + DB_PRINT("\n"); + SDRequest request; + uint8_t r[16]; + request.cmd = 12; + request.arg = 0; + if (s->transfer_mode & SDHCI_TRNS_ACMD12) { + sd_do_command(s->sd, &request, r); + } + s->present_state &= ~(SDHCI_DOING_READ | SDHCI_DOING_WRITE); + s->int_status |= SDHCI_INT_DATA_END; + sdhci_set_irq(s); +} + +static void pio_end_of_block(SDHCIState *s) +{ + DB_PRINT("\n"); + if (!(s->transfer_mode & SDHCI_TRNS_MULTI)) { + sdhci_end_of_transfer(s); + return; + } + + if ((s->transfer_mode & SDHCI_TRNS_BLK_CNT_EN)) { + if (!s->block_count) { + sdhci_end_of_transfer(s); + return; + } + s->block_count--; + } + s->pio_btt = s->block_size; + s->int_status |= + (s->present_state & SDHCI_DOING_READ) ? SDHCI_INT_DATA_AVAIL : 0; + s->int_status |= + (s->present_state & SDHCI_DOING_WRITE) ? SDHCI_INT_SPACE_AVAIL : 0; +} + +static uint32_t do_pio_read(SDHCIState *s) +{ + int i; + uint32_t ret = 0; + if (!(s->present_state & SDHCI_DOING_READ)) { + DB_PRINT("reading pio data register in non-write state"); + return 0; + } + for (i = 0; i < 4; ++i) { + ret = (ret >> 8) | ((uint32_t)sd_read_data(s->sd) << 24); + } + s->pio_btt -= 4; + if (!s->pio_btt) { + pio_end_of_block(s); + } + return ret; +} + +static void do_pio_write(SDHCIState *s, uint32_t value) +{ + int i; + DB_PRINT("btt:%x\n", s->pio_btt); + if (!(s->present_state & SDHCI_DOING_WRITE)) { + DB_PRINT("writing pio data register in non-write state"); + return; + } + for (i = 0; i < 4; ++i) { + sd_write_data(s->sd, (value >> (i*8)) & 0xff); + } + s->pio_btt -= 4; + if (!s->pio_btt) { + pio_end_of_block(s); + } +} + +static void sdhci_pio_transfer(SDHCIState *s) +{ + DB_PRINT("\n"); + s->present_state |= (s->transfer_mode & SDHCI_TRNS_READ) ? + SDHCI_DOING_READ : SDHCI_DOING_WRITE; + pio_end_of_block(s); +} + + +struct adma_desc { + uint16_t flags; + uint16_t size; + uint32_t addr; +}; + +static int sdhci_get_adma_desc(SDHCIState *s, struct adma_desc *ret, + uint32_t adma1_size) +{ + if (s->host_control & SDHCI_CTRL_ADMA32) { /* ADMA2 */ + cpu_physical_memory_read(s->adma_address, (void *)ret, sizeof(*ret)); + return sizeof(*ret); + } else { /* ADMA1 */ + uint32_t adma1; + cpu_physical_memory_read(s->adma_address, (void *)&adma1, + sizeof(adma1)); + ret->addr = adma1 & 0xfffff000; + ret->size = adma1_size; + ret->flags = adma1 & 0x3f; + return sizeof(adma1); + } +} + +static void sdhci_dma_transfer(SDHCIState *s) +{ + int b; + uint16_t xfer_size; + + uint32_t total_size, remaining_size; + + struct adma_desc desc; + int adma_stride = 0; + s->adma_error = 0; + uint32_t adma1_size = (4 << 10); + + DB_PRINT(""); + + if (s->host_control & SDHCI_CTRL_DMA_MASK) { + adma_stride = sdhci_get_adma_desc(s, &desc, adma1_size); + } else { /* use SDMA */ + /* Generate hardcoded descriptor to emulate ADMA */ + desc.addr = s->sdma_address; + desc.size = s->block_count * (s->block_size & 0xfff); + desc.flags = ADMA_VALID | ADMA_END | ADMA_OP_TRAN; + } + /* 0 in the size field means 65536 bytes */ + remaining_size = desc.size ? desc.size : 65536; + + s->int_status |= SDHCI_INT_DATA_END; + + total_size = s->block_count*(s->block_size & 0xfff); + while (total_size) { + /* if the descriptor is invalid OR the desciptor is done OR the + * descriptor is not a transfer operation ... + */ + while (!(desc.flags & ADMA_VALID) || !remaining_size || + ((desc.flags & ADMA_OP_MASK) != ADMA_OP_TRAN)) { + if ((desc.flags & ADMA_END) || !(desc.flags & ADMA_VALID)) { + /* Abort ADMA transfer */ + s->int_status |= SDHCI_INT_ADMA_ERROR; + s->adma_error = 1 /* ST_FDS */; + s->block_count = total_size / s->block_size; + return; + } + /* ADMA operation */ + if ((desc.flags & ADMA_OP_MASK) == ADMA_OP_SET) { + adma1_size = (desc.addr >> 12) & 0xFFFF; + } + if ((desc.flags & ADMA_OP_MASK) == ADMA_OP_LINK) { + s->adma_address = desc.addr; + } else { + s->adma_address += adma_stride; + } + adma_stride = sdhci_get_adma_desc(s, &desc, adma1_size); + /* 0 in the size field means 65536 bytes */ + remaining_size = desc.size ? desc.size : 65536; + } + + xfer_size = MIN((s->block_size & 0xfff), remaining_size); + for (b = 0; b < xfer_size; b++, desc.addr++) { + if (s->transfer_mode & SDHCI_TRNS_READ) { + uint8_t data = sd_read_data(s->sd); + cpu_physical_memory_write(desc.addr, &data, 1); + } else { + uint8_t data; + cpu_physical_memory_read(desc.addr, &data, 1); + sd_write_data(s->sd, data); + } + } + remaining_size -= xfer_size; + total_size -= xfer_size; + } + sdhci_end_of_transfer(s); + s->block_count = 0; +} + +static void sdhci_command(SDHCIState *s, uint32_t cmd) +{ + SDRequest request; + int len; + uint8_t r[16]; + + DB_PRINT("SDHCI command %08x\n", cmd); + if (!s->sd) { /* no SD device attached to the controller */ + s->int_status |= SDHCI_INT_TIMEOUT; + sdhci_set_irq(s); + return; + } + + request.cmd = (cmd>>8) & 0xff; + request.arg = s->arg; + len = sd_do_command(s->sd, &request, r); + if (len == 0) { + if (cmd & SDHCI_CMD_RESP_MASK) { + /* no response expected */ + s->int_status |= SDHCI_INT_INDEX; + DB_PRINT("no response expected %08x\n", cmd); + } else { + /* error */ + s->int_status |= SDHCI_INT_RESPONSE; + DB_PRINT("error response %08x\n", cmd); + } + } else { + DB_PRINT("actual response %08x\n", cmd); + if (len == 4) { + s->response[0] = (r[0]<<24) | (r[1]<<16) | (r[2]<<8) | r[3]; + } else if (len == 16) { + s->response[0] = (r[11]<<24) | (r[12]<<16) | (r[13]<<8) | r[14]; + s->response[1] = (r[7]<<24) | (r[8]<<16) | (r[9]<<8) | r[10]; + s->response[2] = (r[3]<<24) | (r[4]<<16) | (r[5]<<8) | r[6]; + s->response[3] = (0xcc<<24) | (r[0]<<16) | (r[1]<<8) | r[2]; + } + s->int_status |= SDHCI_INT_RESPONSE; + if ((cmd & 3) == SDHCI_CMD_RESP_SHORT_BUSY) { + /* the command will trigger the busy (DAT[0]) line ON then OFF + * this will raise the Data END interrupt when done. + */ + DB_PRINT("raising completion interrupt %08x\n", cmd); + s->int_status |= SDHCI_INT_DATA_END; + } + if (cmd & SDHCI_CMD_DATA) { + if (s->transfer_mode & SDHCI_TRNS_DMA) { + sdhci_dma_transfer(s); + } else { + sdhci_pio_transfer(s); + } + } + } + sdhci_set_irq(s); +} + +static uint32_t sdhci_read32i(void *opaque, target_phys_addr_t offset) +{ + SDHCIState *s = opaque; + + switch (offset) { + case SDHCI_DMA_ADDRESS: + return s->sdma_address; + case SDHCI_BLOCK_SIZE /* +SDHCI_BLOCK_COUNT */: + return s->block_size | (s->block_count << 16); + case SDHCI_ARGUMENT: + return s->arg; + case SDHCI_TRANSFER_MODE /* +SDHCI_COMMAND */: + return s->transfer_mode; + case SDHCI_RESPONSE: + case SDHCI_RESPONSE+4: + case SDHCI_RESPONSE+8: + case SDHCI_RESPONSE+12: + return s->response[(offset-SDHCI_RESPONSE)/sizeof(uint32_t)]; + case SDHCI_BUFFER: + return do_pio_read(s); + case SDHCI_PRESENT_STATE: + return s->present_state | (s->sd ? 0x00170000 : 0) | + (s->bs && bdrv_is_read_only(s->bs) ? 0 : 0x00080000); + case SDHCI_HOST_CONTROL: + /*+SDHCI_POWER_CONTROL +SDHCI_BLOCK_GAP_CONTROL +SDHCI_WAKE_UP_CONTROL*/ + return s->host_control; + case SDHCI_CLOCK_CONTROL /* +SDHCI_TIMEOUT_CONTROL +SDHCI_SOFTWARE_RESET */: + return s->clock | (0<<24 /* reset done */); + case SDHCI_INT_STATUS: + return s->int_status; + case SDHCI_INT_ENABLE: + return s->int_enable; + case SDHCI_SIGNAL_ENABLE: + return 0; + case SDHCI_ACMD12_ERR: + return 0; + case SDHCI_CAPABILITIES: + return SDHCI_SLOT_TYPE_EMBED | SDHCI_CAN_VDD_330 | + SDHCI_CAN_DO_ADMA1 | SDHCI_CAN_DO_HISPD | SDHCI_CAN_DO_SDMA | + SDHCI_CAN_DO_8BIT | SDHCI_CAN_DO_ADMA2 | + (52 << SDHCI_CLOCK_BASE_SHIFT) | + SDHCI_TIMEOUT_CLK_UNIT /* MHz */ | (52<adma_error; + case SDHCI_ADMA_ADDRESS: + return s->adma_address; + case SDHCI_SLOT_INT_STATUS /* +SDHCI_HOST_VERSION */: + return 0 | (SDHCI_SPEC_200 << 16); + default: + DB_PRINT("Read from out of range offset"); + } + + return 0; +} + +static uint64_t sdhci_read(void *opaque, target_phys_addr_t offset, + unsigned size) +{ + uint32_t ret = sdhci_read32i(opaque, offset & ~0x3); + ret >>= (offset & 0x3) * 8; + ret &= ~(-1ULL << (size * 8)); + DB_PRINT("addr=" TARGET_FMT_plx " = %x\n", offset, ret); + return ret; +} + +static void sdhci_write_masked(void *opaque, target_phys_addr_t offset, + uint32_t value, uint32_t mask) +{ + DB_PRINT("addr=" TARGET_FMT_plx " = %x, mask = %08x\n", offset, value, + mask); + SDHCIState *s = opaque; + + switch (offset) { + case SDHCI_DMA_ADDRESS: + MASKED_WRITE(s->sdma_address, value, mask); + break; + case SDHCI_BLOCK_SIZE /* +SDHCI_BLOCK_COUNT */: + MASKED_WRITE(s->block_size, value & 0x7fff, mask); + MASKED_WRITE(s->block_count, value >> 16, mask >> 16); + break; + case SDHCI_ARGUMENT: + MASKED_WRITE(s->arg, value, mask); + break; + case SDHCI_TRANSFER_MODE /* +SDHCI_COMMAND */: + MASKED_WRITE(s->transfer_mode, value & 0x3f, mask); + if (mask & 0xffff0000) { + sdhci_command(s, value >> 16); + } + break; + case SDHCI_BUFFER: + do_pio_write(s, value); + break; + case SDHCI_HOST_CONTROL: + /*+SDHCI_POWER_CONTROL +SDHCI_BLOCK_GAP_CONTROL +SDHCI_WAKE_UP_CONTROL*/ + MASKED_WRITE(s->host_control, value, mask); + if ((mask >> 8) & SDHCI_POWER_ON) { + if (s->sd) { + sd_enable(s->sd, ((value & mask) >> 8) & SDHCI_POWER_ON); + } + } + break; + case SDHCI_CLOCK_CONTROL /* +SDHCI_TIMEOUT_CONTROL +SDHCI_SOFTWARE_RESET */: + if (((value & mask) >> 24) & SDHCI_RESET_ALL) { + sdhci_reset(opaque); + } + if (((value & mask) >> 24) & SDHCI_RESET_CMD) { + s->int_status &= ~SDHCI_INT_RESPONSE; + sdhci_set_irq(s); + } + if (((value & mask) >> 24) & SDHCI_RESET_DATA) { + s->adma_error = 0; + s->int_status &= ~(SDHCI_INT_DATA_END | SDHCI_INT_DMA_END); + sdhci_set_irq(s); + } + MASKED_WRITE(s->clock, (value & 0xfffff) | SDHCI_CLOCK_INT_STABLE, + mask); + break; + case SDHCI_INT_STATUS: + s->int_status &= ~(value & mask); + sdhci_set_irq(s); + break; + case SDHCI_INT_ENABLE: + MASKED_WRITE(s->int_enable, value, mask); + sdhci_set_irq(s); + break; + case SDHCI_SIGNAL_ENABLE: + break; + case SDHCI_SET_ACMD12_ERROR /* +SDHCI_SET_INT_ERROR */: + break; + case SDHCI_ADMA_ADDRESS: + MASKED_WRITE(s->adma_address, value, mask); + break; + case SDHCI_SLOT_INT_STATUS /* +SDHCI_HOST_VERSION */: + break; + /* Read only registers */ + case SDHCI_RESPONSE: + case SDHCI_RESPONSE+4: + case SDHCI_RESPONSE+8: + case SDHCI_RESPONSE+12: + case SDHCI_PRESENT_STATE: + case SDHCI_ACMD12_ERR: + case SDHCI_CAPABILITIES: + case SDHCI_CAPABILITIES_1: + case SDHCI_MAX_CURRENT: + case SDHCI_ADMA_ERROR: + DB_PRINT("Ignored write to read only register"); + break; + default: + DB_PRINT("Ignored write to out of range offset"); + } +} + +static void sdhci_write(void *opaque, target_phys_addr_t offset, + uint64_t value, unsigned size) +{ + uint32_t mask = (1ULL << (size * 8)) - 1; + int shift = 8 * (offset & 0x3); + sdhci_write_masked(opaque, offset & ~0x3, value << shift, mask << shift); +} + +static const MemoryRegionOps sdhci_ops = { + .read = sdhci_read, + .write = sdhci_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const VMStateDescription sdhci_vmstate = { + .name = "sdhci", + .version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT32(sdma_address, SDHCIState), + VMSTATE_UINT16(block_size, SDHCIState), + VMSTATE_UINT16(block_count, SDHCIState), + VMSTATE_UINT32(arg, SDHCIState), + VMSTATE_UINT16(transfer_mode, SDHCIState), + VMSTATE_UINT32_ARRAY(response, SDHCIState, 4), + VMSTATE_UINT32(clock, SDHCIState), + VMSTATE_UINT32(int_enable, SDHCIState), + VMSTATE_UINT32(int_status, SDHCIState), + VMSTATE_UINT32(adma_address, SDHCIState), + VMSTATE_UINT8(adma_error, SDHCIState), + VMSTATE_END_OF_LIST() + } +}; + +static int sdhci_sysbus_init(SysBusDevice *dev) +{ + SDHCIState *s = FROM_SYSBUS(SDHCIState, dev); + + if (s->bs) { + bdrv_detach_dev(s->bs, s); + } + sysbus_init_irq(dev, &s->irq); + memory_region_init_io(&s->iomem, &sdhci_ops, s, "sdhci", 0x100); + sysbus_init_mmio(dev, &s->iomem); + + return 0; +} + +static Property sdhci_properties[] = { + DEFINE_PROP_DRIVE("block", SDHCIState, bs), + DEFINE_PROP_END_OF_LIST(), +}; + +static void sdhci_sysbus_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass); + + sdc->init = sdhci_sysbus_init; + dc->props = sdhci_properties; + dc->vmsd = &sdhci_vmstate; + dc->reset = sdhci_reset; +} + +static TypeInfo sdhci_sysbus_info = { + .name = "sysbus_sdhci", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(SDHCIState), + .class_init = sdhci_sysbus_class_init, +}; + +static void sdhci_register_types(void) +{ + type_register_static(&sdhci_sysbus_info); +} + +type_init(sdhci_register_types)