From owner-freebsd-embedded@FreeBSD.ORG Tue Jul 2 22:18:40 2013 Return-Path: Delivered-To: freebsd-embedded@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:1900:2254:206a::19:1]) by hub.freebsd.org (Postfix) with ESMTP id EEDBC21E; Tue, 2 Jul 2013 22:18:40 +0000 (UTC) (envelope-from kibab@olymp.kibab.com) Received: from olymp.kibab.com (olymp.kibab.com [5.9.14.202]) by mx1.freebsd.org (Postfix) with ESMTP id B9A911996; Tue, 2 Jul 2013 22:18:39 +0000 (UTC) X-DKIM: OpenDKIM Filter v2.5.2 olymp.kibab.com BE2543F47B DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=bakulin.de; s=default; t=1372803517; bh=Iv0nerWML0jQUS5DQ3EYBpkST273JQO0pWUACRyvkvk=; h=Date:From:To:Cc:Subject:References:In-Reply-To; b=a0U90VYaUK5FPKBGNCx+BsKSGInPB8EU8d+fRYnECOvuZNemqdSOLXQxJ2gCOoP2e jMZa1PAtWc6XbHQBm69UO87Zb0C9U11CttYkdyU4aKzamggym81AVrTCgNf/maoWB0 lMwRrq33pdvqsMLHUGgxs5P+czrcOHy3lhW+uDJ0= Date: Wed, 3 Jul 2013 00:18:37 +0200 From: Ilya Bakulin To: Warner Losh Subject: Re: [PATCH] SDIO support for Globalscale Dreamplug Message-ID: <20130702221837.GA76081@olymp.kibab.com> References: <20130702145905.GA1847@olymp.kibab.com> <51D3097A.8010601@FreeBSD.org> <67798E6A-3B0E-4C2F-AD0F-7D3FF3B0A127@bsdimp.com> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <67798E6A-3B0E-4C2F-AD0F-7D3FF3B0A127@bsdimp.com> User-Agent: Mutt/1.5.21 (2010-09-15) Cc: Jack Wilborn , freebsd-arm@freebsd.org, Alexander Motin , freebsd-embedded@freebsd.org X-BeenThere: freebsd-embedded@freebsd.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: Dedicated and Embedded Systems List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 02 Jul 2013 22:18:41 -0000 On Tue, Jul 02, 2013 at 01:32:37PM -0600, Warner Losh wrote: > >>> * Where should I store information retrieved from the CIS? > > The PC Card bus, which also has a CIS, stores it in the slot that it has for a card. In PC Card land, the CIS is shared between multiple devices, so we divide things up accordingly. [...] > > That's quite similar to how we do it for PC Card and CardBus. OK, I have modified my code to store per-function information in the tail queue. Function #0 is special in SDIO, so I chose to store its info separately. Here is an updated patch. Please don't blame me much :-) diff --git a/sys/arm/conf/KIBAB-DPLUG b/sys/arm/conf/KIBAB-DPLUG new file mode 100644 index 0000000..033d398 --- /dev/null +++ b/sys/arm/conf/KIBAB-DPLUG @@ -0,0 +1,171 @@ +# Kernel config for GlobalScale Technologies DreamPlug version 1001. +# +# This is for units that are version 10, revision 01, with NOR SPI flash. +# These units are identified with the number "1001" on the S/N label. +# +# For more information on this file, please read the handbook section on +# Kernel Configuration Files: +# +# http://www.FreeBSD.org/doc/en_US.ISO8859-1/books/handbook/kernelconfig-config.html +# +# The handbook is also available locally in /usr/share/doc/handbook +# if you've installed the doc distribution, otherwise always see the +# FreeBSD World Wide Web server (http://www.FreeBSD.org/) for the +# latest information. +# +# An exhaustive list of options and more detailed explanations of the +# device lines is also present in the ../../conf/NOTES and NOTES files. +# If you are in doubt as to the purpose or necessity of a line, check first +# in NOTES. +# +# $FreeBSD$ +# + +ident KIBAB-DPLUG + +include "../mv/kirkwood/std.db88f6xxx" + +makeoptions FDT_DTS_FILE=dreamplug-1001.dts + +makeoptions MODULES_OVERRIDE="" + +options SOC_MV_KIRKWOOD + +options SCHED_4BSD #4BSD scheduler +options INET #InterNETworking +options INET6 #IPv6 communications protocols +options SOFTUPDATES +options CD9660 #ISO 9660 filesystem +options FFS #Berkeley Fast Filesystem +options MSDOSFS #MS DOS File System (FAT, FAT32) +options NULLFS #NULL filesystem +options TMPFS #Efficient memory filesystem +options SYSVSHM #SYSV-style shared memory +options SYSVMSG #SYSV-style message queues +options SYSVSEM #SYSV-style semaphores +options _KPOSIX_PRIORITY_SCHEDULING #Posix P1003_1B real-time extensions +options GEOM_ELI # Disk encryption. +options GEOM_LABEL # Providers labelization. +options GEOM_PART_GPT # GPT partitioning + +# Flattened Device Tree +device fdt +options FDT +options FDT_DTB_STATIC + +# Misc pseudo devices +device bpf #Required for DHCP +device faith #IPv6-to-IPv4 relaying (translation) +device firmware #firmware(9) required for USB wlan +device gif #IPv6 and IPv4 tunneling +device loop #Network loopback +device md #Memory/malloc disk +device pty #BSD-style compatibility pseudo ttys +device random #Entropy device +device tun #Packet tunnel. +device ether #Required for all ethernet devices +device vlan #802.1Q VLAN support +device wlan #802.11 WLAN support + +# cam support for umass and ahci +device scbus +device pass +device da +device cd + +# Serial ports +device uart + +# Networking +device mge # Marvell Gigabit Ethernet controller +device mii +device e1000phy + +# USB +options USB_HOST_ALIGN=32 # Align DMA to cacheline +#options USB_DEBUG # Compile in USB debug support +device usb # Basic usb support +device ehci # USB host controller +device umass # Mass storage +device uhid # Human-interface devices +device rum # Ralink Technology RT2501USB wireless NICs + +# I2C (TWSI) +device iic +device iicbus + +# SATA +device mvs +device ahci + +# SDIO +device mv_sdio +device mmcsd +device mmc + +# Sound +device sound +device snd_uaudio + +#crypto +device cesa # Marvell security engine +device crypto +device cryptodev + +# IPSec +device enc +options IPSEC +options IPSEC_NAT_T +options TCP_SIGNATURE #include support for RFC 2385 + +#PF +device pf +device pflog +device pfsync + +# ALTQ, required for PF +options ALTQ # Basic ALTQ support +options ALTQ_CBQ # Class Based Queueing +options ALTQ_RED # Random Early Detection +options ALTQ_RIO # RED In/Out +options ALTQ_HFSC # Hierarchical Packet Scheduler +options ALTQ_CDNR # Traffic conditioner +options ALTQ_PRIQ # Priority Queueing +options ALTQ_NOPCC # Required if the TSC is unusable +#options ALTQ_DEBUG + +# Debugging +makeoptions DEBUG=-g #Build kernel with gdb(1) debug symbols +options BREAK_TO_DEBUGGER +options ALT_BREAK_TO_DEBUGGER +options DDB +options KDB +options DIAGNOSTIC +options INVARIANTS #Enable calls of extra sanity checking +options INVARIANT_SUPPORT #Extra sanity checks of internal structures, required by INVARIANTS +#options WITNESS #Enable checks to detect deadlocks and cycles +#options WITNESS_SKIPSPIN #Don't run witness on spinlocks for speed +#options WITNESS_KDB + +# Enable these options for nfs root configured via BOOTP. +options NFSCL #Network Filesystem Client +options NFSLOCKD #Network Lock Manager +options NFS_ROOT #NFS usable as /, requires NFSCLIENT +options BOOTP +options BOOTP_NFSROOT +#options BOOTP_NFSV3 +options BOOTP_WIRED_TO=mge0 + +# If not using BOOTP, use something like one of these... +#options ROOTDEVNAME=\"ufs:/dev/da1a\" +#options ROOTDEVNAME=\"ufs:/dev/da1s1a\" +#options ROOTDEVNAME=\"ufs:/dev/da1p10\" +#options ROOTDEVNAME=\"nfs:192.168.0.254/dreamplug\" + +# To use this configuration with the (rare) model 1001N (nand flash), +# create a kernel config file that looks like this: +# +# include DREAMPLUG-1001 +# nomakeoptions FDT_DTS_FILE +# makeoptions FDT_DTS_FILE=dreamplug-1001N.dts +# device nand diff --git a/sys/arm/conf/KIBAB-DPLUG-NODBG b/sys/arm/conf/KIBAB-DPLUG-NODBG new file mode 100644 index 0000000..cbced4a --- /dev/null +++ b/sys/arm/conf/KIBAB-DPLUG-NODBG @@ -0,0 +1,43 @@ +# Kernel config for GlobalScale Technologies DreamPlug version 1001. +# +# This is for units that are version 10, revision 01, with NOR SPI flash. +# These units are identified with the number "1001" on the S/N label. +# +# For more information on this file, please read the handbook section on +# Kernel Configuration Files: +# +# http://www.FreeBSD.org/doc/en_US.ISO8859-1/books/handbook/kernelconfig-config.html +# +# The handbook is also available locally in /usr/share/doc/handbook +# if you've installed the doc distribution, otherwise always see the +# FreeBSD World Wide Web server (http://www.FreeBSD.org/) for the +# latest information. +# +# An exhaustive list of options and more detailed explanations of the +# device lines is also present in the ../../conf/NOTES and NOTES files. +# If you are in doubt as to the purpose or necessity of a line, check first +# in NOTES. +# +# $FreeBSD$ +# + +ident KIBAB-DPLUG-NODBG + +include KIBAB-DPLUG + +# Do not compile FDT in kernel +nomakeoptions FDT_DTS_FILE +nooptions FDT_DTB_STATIC + +# Debugging +nomakeoptions DEBUG +nooptions BREAK_TO_DEBUGGER +nooptions ALT_BREAK_TO_DEBUGGER +nooptions DDB +nooptions KDB +nooptions DIAGNOSTIC +nooptions INVARIANTS #Enable calls of extra sanity checking +nooptions INVARIANT_SUPPORT #Extra sanity checks of internal structures, required by INVARIANTS +#options WITNESS #Enable checks to detect deadlocks and cycles +#options WITNESS_SKIPSPIN #Don't run witness on spinlocks for speed +#options WITNESS_KDB diff --git a/sys/arm/conf/KIBAB-DPLUG-PROD b/sys/arm/conf/KIBAB-DPLUG-PROD new file mode 100644 index 0000000..bae61a4 --- /dev/null +++ b/sys/arm/conf/KIBAB-DPLUG-PROD @@ -0,0 +1,33 @@ +# Kernel config for GlobalScale Technologies DreamPlug version 1001. +# +# This is for units that are version 10, revision 01, with NOR SPI flash. +# These units are identified with the number "1001" on the S/N label. +# +# For more information on this file, please read the handbook section on +# Kernel Configuration Files: +# +# http://www.FreeBSD.org/doc/en_US.ISO8859-1/books/handbook/kernelconfig-config.html +# +# The handbook is also available locally in /usr/share/doc/handbook +# if you've installed the doc distribution, otherwise always see the +# FreeBSD World Wide Web server (http://www.FreeBSD.org/) for the +# latest information. +# +# An exhaustive list of options and more detailed explanations of the +# device lines is also present in the ../../conf/NOTES and NOTES files. +# If you are in doubt as to the purpose or necessity of a line, check first +# in NOTES. +# +# $FreeBSD$ +# + + +include KIBAB-DPLUG-NODBG + +ident KIBAB-DPLUG-PROD + +nooptions NFS_ROOT +nooptions BOOTP +nooptions BOOTP_NFSROOT +nooptions BOOTP_WIRED_TO + diff --git a/sys/arm/mv/files.mv b/sys/arm/mv/files.mv index 116356d..88c0b98 100644 --- a/sys/arm/mv/files.mv +++ b/sys/arm/mv/files.mv @@ -32,6 +32,7 @@ arm/mv/mv_sata.c optional ata | atamvsata arm/mv/mv_ts.c standard arm/mv/timer.c standard arm/mv/twsi.c optional iicbus +arm/mv/mv_sdio.c optional mv_sdio dev/cesa/cesa.c optional cesa dev/mge/if_mge.c optional mge diff --git a/sys/arm/mv/mv_sdio.c b/sys/arm/mv/mv_sdio.c new file mode 100644 index 0000000..73faf08 --- /dev/null +++ b/sys/arm/mv/mv_sdio.c @@ -0,0 +1,1670 @@ +/*- + * Copyright (c) 2009 Semihalf, Rafal Czubak + * All rights reserved. + * + * 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. + */ + +/* + * Driver for Marvell Integrated SDIO Host Controller. + * Works stable in DMA mode. PIO mode has problems with large data transfers + * (timeouts). + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "mmcbr_if.h" + +#include "mv_sdio.h" + +/* Minimum DMA segment size. */ +#define MV_SDIO_DMA_SEGMENT_SIZE 4096 + +/* Transferred block size. */ +#define MV_SDIO_BLOCK_SIZE 512 + +/* Maximum number of blocks the controller can handle. */ +#define MV_SDIO_BLOCKS_MAX 65535 + +/* Halfword bit masks used for command response extraction. */ +#define MV_SDIO_RSP48_BM2 0x0002 /* Lower 2 bits. */ +#define MV_SDIO_RSP48_BM6 0x003f /* Lower 6 bits. */ +#define MV_SDIO_RSP48_BM16 0xffff /* 16 bits */ + +/* SDIO aggregated command interrupts */ +#define MV_SDIO_IRQS_CMD (MV_SDIO_IRQ_CMD | MV_SDIO_IRQ_UNEXPECTED_RSP) +#define MV_SDIO_EIRQS_CMD (MV_SDIO_EIRQ_CMD_TMO | MV_SDIO_EIRQ_CMD_CRC7 | \ + MV_SDIO_EIRQ_CMD_ENDBIT | MV_SDIO_EIRQ_CMD_INDEX | \ + MV_SDIO_EIRQ_CMD_STARTBIT | MV_SDIO_EIRQ_RSP_TBIT) + +/* SDIO aggregated data interrupts */ +#define MV_SDIO_IRQS_DATA (MV_SDIO_IRQ_XFER | MV_SDIO_IRQ_TX_EMPTY | \ + MV_SDIO_IRQ_RX_FULL | MV_SDIO_IRQ_DMA | MV_SDIO_IRQ_AUTOCMD12) +#define MV_SDIO_EIRQS_DATA (MV_SDIO_EIRQ_DATA_TMO | \ + MV_SDIO_EIRQ_DATA_CRC16 | MV_SDIO_EIRQ_DATA_ENDBIT | \ + MV_SDIO_EIRQ_AUTOCMD12 | MV_SDIO_EIRQ_XFER_SIZE | \ + MV_SDIO_EIRQ_CRC_ENDBIT | MV_SDIO_EIRQ_CRC_STARTBIT | \ + MV_SDIO_EIRQ_CRC_STAT) + +/* + * Timing configuration. + */ + +/* SDIO controller base clock frequency. */ +#define MV_SDIO_F_BASE 100000000 /* 200 MHz */ + +/* Maximum SD clock frequency. */ +#define MV_SDIO_F_MAX (MV_SDIO_F_BASE / 2) /* 50 MHz */ + +/* Maximum timeout value. */ +#define MV_SDIO_TMO_MAX 0xf + +/* Reset delay in microseconds. */ +#define MV_SDIO_RESET_DELAY 10000 /* 10 ms */ + +/* Empty FIFO polling delay. */ +#define MV_SDIO_FIFO_EMPTY_DELAY 1000 /* 1 ms */ + +/* Delays between operations on multiple blocks. */ +#define MV_SDIO_RD_DELAY 50 /*50*/ /* Read access time. */ +#define MV_SDIO_WR_DELAY 10 /*10*/ /* Write access time. */ + +/* Maximum clock divider value. */ +#define MV_SDIO_CLK_DIV_MAX 0x7ff + +struct mv_sdio_softc { + device_t sc_dev; + device_t sc_child; + + bus_space_handle_t sc_bsh; + bus_space_tag_t sc_bst; + + int sc_use_dma; + bus_dma_tag_t sc_dmatag; + bus_dmamap_t sc_dmamap; + uint8_t *sc_dmamem; + bus_addr_t sc_physaddr; + int sc_mapped; + size_t sc_dma_size; + + struct resource *sc_mem_res; + int sc_mem_rid; + + struct resource *sc_irq_res; + int sc_irq_rid; + void *sc_ihl; + + struct resource *sc_cd_irq_res; + int sc_cd_irq_rid; + void *sc_cd_ihl; + + uint32_t sc_irq_mask; + uint32_t sc_eirq_mask; + + struct task sc_card_task; + struct callout sc_card_callout; + + struct mtx sc_mtx; + + int sc_bus_busy; + int sc_card_present; + struct mmc_host sc_host; + struct mmc_request *sc_req; + struct mmc_command *sc_curcmd; + + uint32_t sc_data_offset; +}; + +/* Read/write data from/to registers.*/ +static uint32_t MV_SDIO_RD4(struct mv_sdio_softc *, bus_size_t); +static void MV_SDIO_WR4(struct mv_sdio_softc *, bus_size_t, uint32_t); + +static int mv_sdio_probe(device_t); +static int mv_sdio_attach(device_t); + +static int mv_sdio_read_ivar(device_t, device_t, int, uintptr_t *); +static int mv_sdio_write_ivar(device_t, device_t, int, uintptr_t); + +static int mv_sdio_update_ios(device_t, device_t); +static int mv_sdio_request(device_t, device_t, struct mmc_request *); +static int mv_sdio_get_ro(device_t, device_t); +static int mv_sdio_acquire_host(device_t, device_t); +static int mv_sdio_release_host(device_t, device_t); + +/* Finalizes active MMC request. */ +static void mv_sdio_finalize_request(struct mv_sdio_softc *); + +/* Initializes controller's registers. */ +static void mv_sdio_init(device_t); + +/* Initializes host structure. */ +static void mv_sdio_init_host(struct mv_sdio_softc *); + +/* Used to add and handle sysctls. */ +static void mv_sdio_add_sysctls(struct mv_sdio_softc *); +static int mv_sdio_sysctl_use_dma(SYSCTL_HANDLER_ARGS); + +/* DMA initialization and cleanup functions. */ +static int mv_sdio_dma_init(struct mv_sdio_softc *); +static void mv_sdio_dma_finish(struct mv_sdio_softc *); + +/* DMA map load callback. */ +static void mv_sdio_getaddr(void *, bus_dma_segment_t *, int, int); + +/* Prepare command/data before transaction. */ +static int mv_sdio_start_command(struct mv_sdio_softc *, struct + mmc_command *); +static int mv_sdio_start_data(struct mv_sdio_softc *, struct mmc_data *); + +/* Finish command after transaction. */ +static void mv_sdio_finish_command(struct mv_sdio_softc *); + +/* Response handling. */ +static void mv_sdio_handle_136bit_resp(struct mv_sdio_softc *); +static void mv_sdio_handle_48bit_resp(struct mv_sdio_softc *, + struct mmc_command *); + +/* Interrupt handler and interrupt helper functions. */ +static void mv_sdio_intr(void *); +static void mv_sdio_cmd_intr(struct mv_sdio_softc *, uint32_t, uint32_t); +static void mv_sdio_data_intr(struct mv_sdio_softc *, uint32_t, uint32_t); +static void mv_sdio_disable_intr(struct mv_sdio_softc *); + +/* Used after card detect interrupt has been handled. */ +static void mv_sdio_card_task(void *, int); + +/* Read/write data from FIFO in PIO mode. */ +static uint32_t mv_sdio_read_fifo(struct mv_sdio_softc *); +static void mv_sdio_write_fifo(struct mv_sdio_softc *, uint32_t); + +/* + * PIO mode handling. + * + * Inspired by sdhci(4) driver routines. + */ +static void mv_sdio_transfer_pio(struct mv_sdio_softc *); +static void mv_sdio_read_block_pio(struct mv_sdio_softc *); +static void mv_sdio_write_block_pio(struct mv_sdio_softc *); + + +static device_method_t mv_sdio_methods[] = { + /* device_if */ + DEVMETHOD(device_probe, mv_sdio_probe), + DEVMETHOD(device_attach, mv_sdio_attach), + + /* Bus interface */ + DEVMETHOD(bus_read_ivar, mv_sdio_read_ivar), + DEVMETHOD(bus_write_ivar, mv_sdio_write_ivar), + + /* mmcbr_if */ + DEVMETHOD(mmcbr_update_ios, mv_sdio_update_ios), + DEVMETHOD(mmcbr_request, mv_sdio_request), + DEVMETHOD(mmcbr_get_ro, mv_sdio_get_ro), + DEVMETHOD(mmcbr_acquire_host, mv_sdio_acquire_host), + DEVMETHOD(mmcbr_release_host, mv_sdio_release_host), + + {0, 0}, +}; + +static driver_t mv_sdio_driver = { + "sdio", + mv_sdio_methods, + sizeof(struct mv_sdio_softc), +}; +static devclass_t mv_sdio_devclass; + +DRIVER_MODULE( sdio, simplebus, mv_sdio_driver, mv_sdio_devclass, 0, 0); + + +static __inline uint32_t +MV_SDIO_RD4(struct mv_sdio_softc *sc, bus_size_t off) +{ + + return (bus_read_4(sc->sc_mem_res, off)); +} + +static __inline void +MV_SDIO_WR4(struct mv_sdio_softc *sc, bus_size_t off, uint32_t val) +{ + + bus_write_4(sc->sc_mem_res, off, val); +} + +static int platform_sdio_slot_signal( int signal ) +{ + switch( signal ) + { + case MV_SDIO_SIG_CD: + { + return -1; + break; + } + case MV_SDIO_SIG_WP: + return 0; + break; + default: + return -1; + break; + } + + return 0; +} + +static int +mv_sdio_probe(device_t dev) +{ + uint32_t device, revision; + + if (!ofw_bus_is_compatible(dev, "mrvl,sdio")) + return (ENXIO); + + + soc_id(&device, &revision); + + switch (device) { + case MV_DEV_88F6281: + break; + default: + return (ENXIO); + } + + device_set_desc(dev, "Marvell Integrated SDIO Host Controller"); + + return (BUS_PROBE_SPECIFIC); +} + +static int +mv_sdio_attach(device_t dev) +{ + struct mv_sdio_softc *sc; + int task_initialized = 0; + + sc = device_get_softc(dev); + sc->sc_dev = dev; + + mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); + + /* Allocate memory and interrupt resources. */ + sc->sc_mem_rid = 0; + sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, + &sc->sc_mem_rid, RF_ACTIVE); + + if (sc->sc_mem_res == NULL) { + device_printf(dev, "Could not allocate memory!\n"); + goto fail; + } + + sc->sc_irq_rid = 0; + sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, + &sc->sc_irq_rid, RF_ACTIVE); + + if (sc->sc_irq_res == NULL) { + device_printf(dev, "Could not allocate IRQ!\n"); + goto fail; + } + + sc->sc_bst = rman_get_bustag(sc->sc_mem_res); + sc->sc_bsh = rman_get_bushandle(sc->sc_mem_res); + + + /* Initialize host controller's registers. */ + mv_sdio_init(dev); + + /* Try to setup DMA. */ + sc->sc_mapped = 0; /* No DMA buffer is mapped. */ + sc->sc_use_dma = 1; /* DMA mode is preferred to PIO mode. */ + + if (mv_sdio_dma_init(sc) < 0) { + device_printf(dev, "Falling back to PIO mode.\n"); + sc->sc_use_dma = 0; + } + + /* Add sysctls. */ + mv_sdio_add_sysctls(sc); + + if (platform_sdio_slot_signal(MV_SDIO_SIG_CD) != -1) { + /* Check if card is present in the slot. */ + if (platform_sdio_slot_signal(MV_SDIO_SIG_CD) == 1) + sc->sc_card_present = 1; + } + + TASK_INIT(&sc->sc_card_task, 0, mv_sdio_card_task, sc); + callout_init(&sc->sc_card_callout, 1); + task_initialized = 1; + + /* Setup interrupt. */ + if (bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_MISC | + INTR_MPSAFE, NULL, mv_sdio_intr, sc, &sc->sc_ihl) != 0) { + device_printf(dev, "Could not setup interrupt!\n"); + goto fail; + } + + /* Host can be acquired. */ + sc->sc_bus_busy = 0; + + /* + * Attach MMC bus only if the card is in the slot or card detect is + * not supported on the platform. + */ + if ((platform_sdio_slot_signal(MV_SDIO_SIG_CD) == -1) || + sc->sc_card_present) { + sc->sc_child = device_add_child(dev, "mmc", -1); + + if (sc->sc_child == NULL) { + device_printf(dev, "Could not add MMC bus!\n"); + goto fail; + } + + /* Initialize host structure for MMC bus. */ + mv_sdio_init_host(sc); + + device_set_ivars(sc->sc_child, &sc->sc_host); + } + + return (bus_generic_attach(dev)); + +fail: + mv_sdio_dma_finish(sc); + if (task_initialized) { + callout_drain(&sc->sc_card_callout); + taskqueue_drain(taskqueue_swi, &sc->sc_card_task); + } + if (sc->sc_ihl != NULL) + bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_ihl); + if (sc->sc_cd_ihl != NULL) + bus_teardown_intr(dev, sc->sc_cd_irq_res, sc->sc_cd_ihl); + if (sc->sc_irq_res != NULL) + bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid, + sc->sc_irq_res); + if (sc->sc_cd_irq_res != NULL) + bus_release_resource(dev, SYS_RES_IRQ, sc->sc_cd_irq_rid, + sc->sc_cd_irq_res); + if (sc->sc_mem_res != NULL) + bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid, + sc->sc_mem_res); + mtx_destroy(&sc->sc_mtx); + return (ENXIO); +} + +static int +mv_sdio_update_ios(device_t brdev, device_t reqdev) +{ + struct mv_sdio_softc *sc; + struct mmc_host *host; + struct mmc_ios *ios; + uint32_t xfer, clk_div, host_cr; + + sc = device_get_softc(brdev); + host = device_get_ivars(reqdev); + ios = &host->ios; + + mtx_lock(&sc->sc_mtx); + + if (ios->power_mode == power_off) + /* Re-initialize the controller. */ + mv_sdio_init(brdev); + + xfer = MV_SDIO_RD4(sc, MV_SDIO_XFER); + + if (ios->clock == 0) { + /* Disable clock. */ + xfer |= MV_SDIO_XFER_STOP_CLK; + MV_SDIO_WR4(sc, MV_SDIO_XFER, xfer); + + /* Set maximum clock divider. */ + MV_SDIO_WR4(sc, MV_SDIO_CLK_DIV, MV_SDIO_CLK_DIV_MAX); + } else { + /* + * Calculate and set clock divider. + * Clock rate value is: + * clock = MV_SDIO_F_BASE / (clk_div + 1) + * Thus we calculate the divider value as: + * clk_div = (MV_SDIO_F_BASE / clock) - 1 + */ + clk_div = (MV_SDIO_F_BASE / ios->clock) - 1; + if (clk_div > MV_SDIO_CLK_DIV_MAX) + clk_div = MV_SDIO_CLK_DIV_MAX; + MV_SDIO_WR4(sc, MV_SDIO_CLK_DIV, clk_div); + + /* Enable clock. */ + xfer &= ~MV_SDIO_XFER_STOP_CLK; + MV_SDIO_WR4(sc, MV_SDIO_XFER, xfer); + } + + host_cr = MV_SDIO_RD4(sc, MV_SDIO_HOST_CR); + + /* Set card type. */ + if (host->mode == mode_mmc) + host_cr |= MV_SDIO_HOST_CR_MMC; /* MMC card. */ + else + host_cr &= ~MV_SDIO_HOST_CR_MMC; /* SD card. */ + + /* Set bus width. */ + if (ios->bus_width == bus_width_4) + host_cr |= MV_SDIO_HOST_CR_4BIT; /* 4-bit bus width */ + else + host_cr &= ~MV_SDIO_HOST_CR_4BIT; /* 1-bit bus width */ + + /* Set high/normal speed mode. */ +#if 0 /* Some cards have problems with the highspeed-mode + * Not selecting High-Speed mode enables all cards to work + */ + + if ((ios->timing == bus_timing_hs ) && ( 1 == 0 ) ) + host_cr |= MV_SDIO_HOST_CR_HIGHSPEED; + else +#endif + host_cr &= ~MV_SDIO_HOST_CR_HIGHSPEED; + + MV_SDIO_WR4(sc, MV_SDIO_HOST_CR, host_cr); + + mtx_unlock(&sc->sc_mtx); + + return (0); +} + +static int +mv_sdio_request(device_t brdev, device_t reqdev, struct mmc_request *req) +{ + struct mv_sdio_softc *sc; + int rv; + + sc = device_get_softc(brdev); + rv = EBUSY; + + mtx_lock(&sc->sc_mtx); + + if (sc->sc_req != NULL) { + mtx_unlock(&sc->sc_mtx); + return (rv); + } + + sc->sc_req = req; +/* + device_printf(sc->sc_dev, "cmd %d (hw state 0x%04x)\n", + req->cmd->opcode , MV_SDIO_RD4( sc, MV_SDIO_HOST_SR ) ); +*/ + rv = mv_sdio_start_command(sc, req->cmd); + + mtx_unlock(&sc->sc_mtx); + + return (rv); +} + +static int +mv_sdio_get_ro(device_t brdev, device_t reqdev) +{ + int rv; + + /* Check if card is read only. */ + rv = platform_sdio_slot_signal(MV_SDIO_SIG_WP); + + /* + * Assume that card is not write protected, when platform doesn't + * support WP signal. + */ + if (rv < 0) + rv = 0; + + return (rv); +} + +static int +mv_sdio_acquire_host(device_t brdev, device_t reqdev) +{ + struct mv_sdio_softc *sc; + int rv; + + sc = device_get_softc(brdev); + rv = 0; + + mtx_lock(&sc->sc_mtx); + while (sc->sc_bus_busy) + rv = mtx_sleep(sc, &sc->sc_mtx, PZERO, "sdioah", 0); + sc->sc_bus_busy++; + mtx_unlock(&sc->sc_mtx); + + return (rv); +} + +static int +mv_sdio_release_host(device_t brdev, device_t reqdev) +{ + struct mv_sdio_softc *sc; + + sc = device_get_softc(brdev); + + mtx_lock(&sc->sc_mtx); + sc->sc_bus_busy--; + wakeup(sc); + mtx_unlock(&sc->sc_mtx); + + return (0); +} + +static void +mv_sdio_finalize_request(struct mv_sdio_softc *sc) +{ + struct mmc_request *req; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + req = sc->sc_req; + + if (req) { + /* Finalize active request. */ + /*device_printf(sc->sc_dev, "Finalize request %i\n",req->cmd->opcode);*/ + sc->sc_req = NULL; + sc->sc_curcmd = NULL; + req->done(req); + + + } else + device_printf(sc->sc_dev, "No active request to finalize!\n"); +} + +static void +mv_sdio_init(device_t dev) +{ + struct mv_sdio_softc *sc; + uint32_t host_cr; + + sc = device_get_softc(dev); + + /* Disable interrupts. */ + sc->sc_irq_mask = 0; + sc->sc_eirq_mask = 0; + MV_SDIO_WR4(sc, MV_SDIO_IRQ_EN, sc->sc_irq_mask); + MV_SDIO_WR4(sc, MV_SDIO_EIRQ_EN, sc->sc_eirq_mask); + + /* Clear interrupt status registers. */ + MV_SDIO_WR4(sc, MV_SDIO_IRQ_SR, MV_SDIO_IRQ_ALL); + MV_SDIO_WR4(sc, MV_SDIO_EIRQ_SR, MV_SDIO_EIRQ_ALL); + + /* Enable interrupt status registers. */ + MV_SDIO_WR4(sc, MV_SDIO_IRQ_SR_EN, MV_SDIO_IRQ_ALL); + MV_SDIO_WR4(sc, MV_SDIO_EIRQ_SR_EN, MV_SDIO_EIRQ_ALL); + + /* Initialize Host Control Register. */ + host_cr = (MV_SDIO_HOST_CR_PUSHPULL | MV_SDIO_HOST_CR_BE | + MV_SDIO_HOST_CR_TMOVAL(MV_SDIO_TMO_MAX) | MV_SDIO_HOST_CR_TMO); + + MV_SDIO_WR4(sc, MV_SDIO_HOST_CR, host_cr); + + /* Stop clock and reset Transfer Mode Register. */ + MV_SDIO_WR4(sc, MV_SDIO_XFER, MV_SDIO_XFER_STOP_CLK); + + /* Set maximum clock divider value. */ + MV_SDIO_WR4(sc, MV_SDIO_CLK_DIV, MV_SDIO_CLK_DIV_MAX); + + /* Reset status, state machine and FIFOs synchronously. */ + MV_SDIO_WR4(sc, MV_SDIO_SW_RESET, MV_SDIO_SW_RESET_ALL); + DELAY(MV_SDIO_RESET_DELAY); +} + +static void +mv_sdio_init_host(struct mv_sdio_softc *sc) +{ + struct mmc_host *host; + + host = &sc->sc_host; + + /* Clear host structure. */ + bzero(host, sizeof(struct mmc_host)); + + /* Calculate minimum and maximum operating frequencies. */ + host->f_min = MV_SDIO_F_BASE / (MV_SDIO_CLK_DIV_MAX + 1); + host->f_max = MV_SDIO_F_MAX; + + /* Set operation conditions (voltage). */ + host->host_ocr = MMC_OCR_320_330 | MMC_OCR_330_340; + + /* Set additional host controller capabilities. */ + host->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_HSPEED; +} + +static void +mv_sdio_add_sysctls(struct mv_sdio_softc *sc) +{ + struct sysctl_ctx_list *ctx; + struct sysctl_oid_list *children; + struct sysctl_oid *tree; + + ctx = device_get_sysctl_ctx(sc->sc_dev); + children = SYSCTL_CHILDREN(device_get_sysctl_tree(sc->sc_dev)); + tree = SYSCTL_ADD_NODE(ctx, children, OID_AUTO, "params", + CTLFLAG_RD, 0, "Driver parameters"); + children = SYSCTL_CHILDREN(tree); + + SYSCTL_ADD_PROC(ctx, children, OID_AUTO, "use_dma", + CTLTYPE_UINT | CTLFLAG_RW, sc, 0, mv_sdio_sysctl_use_dma, + "I", "Use DMA for data transfers (0-1)"); +} + +/* + * This sysctl allows switching between DMA and PIO modes for data transfers: + * + * dev.mv_sdio..params.use_dma + * + * Values: + * + * - 1 sets DMA mode + * - 0 sets PIO mode + * + * Driver uses DMA mode by default. + */ +static int +mv_sdio_sysctl_use_dma(SYSCTL_HANDLER_ARGS) +{ + struct mv_sdio_softc *sc; + uint32_t use_dma; + int error; + + sc = (struct mv_sdio_softc *)arg1; + + use_dma = sc->sc_use_dma; + + error = sysctl_handle_int(oidp, &use_dma, 0, req); + if (error != 0 || req->newptr == NULL) + return (error); + + if (use_dma > 1) + return (EINVAL); + + mtx_lock(&sc->sc_mtx); + + /* Check if requested mode is already being used. */ + if (sc->sc_use_dma == use_dma) { + mtx_unlock(&sc->sc_mtx); + return (EPERM); + } + + if (!(sc->sc_mapped)) { + device_printf(sc->sc_dev, "DMA not initialized!\n"); + mtx_unlock(&sc->sc_mtx); + return (ENOMEM); + } + + /* Set new mode. */ + sc->sc_use_dma = use_dma; + + mtx_unlock(&sc->sc_mtx); + + return (0); +} + +static void +mv_sdio_getaddr(void *arg, bus_dma_segment_t *segs, int nsegs, int error) +{ + + if (error != 0) + return; + + /* Get first segment's physical address. */ + *(bus_addr_t *)arg = segs->ds_addr; +} + +static int +mv_sdio_dma_init(struct mv_sdio_softc *sc) +{ + device_t dev; + bus_size_t dmabuf_size; + + dev = sc->sc_dev; + dmabuf_size = MAXPHYS; + + /* Create DMA tag. */ + if (bus_dma_tag_create(bus_get_dma_tag(dev), /* parent */ + MV_SDIO_DMA_SEGMENT_SIZE, 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filtfunc, filtfuncarg */ + MAXPHYS, 1, /* maxsize, nsegments */ + MAXPHYS, BUS_DMA_ALLOCNOW, /* maxsegsz, flags */ + NULL, NULL, /* lockfunc, lockfuncarg */ + &sc->sc_dmatag) != 0) { + device_printf(dev, "Could not create DMA tag!\n"); + return (-1); + } + + /* Allocate DMA memory. */ + if (bus_dmamem_alloc(sc->sc_dmatag, (void **)&sc->sc_dmamem, + BUS_DMA_NOWAIT, &sc->sc_dmamap) != 0) { + device_printf(dev, "Could not allocate DMA memory!\n"); + mv_sdio_dma_finish(sc); + return (-1); + } + + /* Find the biggest available DMA buffer size. */ + while (bus_dmamap_load(sc->sc_dmatag, sc->sc_dmamap, + (void *)sc->sc_dmamem, dmabuf_size, mv_sdio_getaddr, + &sc->sc_physaddr, 0) != 0) { + dmabuf_size >>= 1; + if (dmabuf_size < MV_SDIO_BLOCK_SIZE) { + device_printf(dev, "Could not load DMA map!\n"); + mv_sdio_dma_finish(sc); + return (-1); + } + } + + sc->sc_mapped++; + sc->sc_dma_size = dmabuf_size; + + return (0); +} + +static void +mv_sdio_dma_finish(struct mv_sdio_softc *sc) +{ + + /* Free DMA resources. */ + if (sc->sc_mapped) { + bus_dmamap_unload(sc->sc_dmatag, sc->sc_dmamap); + sc->sc_mapped--; + } + if (sc->sc_dmamem != NULL) + bus_dmamem_free(sc->sc_dmatag, sc->sc_dmamem, sc->sc_dmamap); + if (sc->sc_dmamap != NULL) + bus_dmamap_destroy(sc->sc_dmatag, sc->sc_dmamap); + if (sc->sc_dmatag != NULL) + bus_dma_tag_destroy(sc->sc_dmatag); +} + +static int +mv_sdio_start_command(struct mv_sdio_softc *sc, struct mmc_command *cmd) +{ + struct mmc_request *req; + uint32_t cmdreg; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + req = sc->sc_req; + + sc->sc_curcmd = cmd; + + cmd->error = MMC_ERR_NONE; + + /* Check if card is in the slot. */ + if ((platform_sdio_slot_signal(MV_SDIO_SIG_CD) != -1) && + (sc->sc_card_present == 0)) { + cmd->error = MMC_ERR_FAILED; + mv_sdio_finalize_request(sc); + return (-1); + } + + /* Check if clock is enabled. */ + if (MV_SDIO_RD4(sc, MV_SDIO_XFER) & MV_SDIO_XFER_STOP_CLK) { + cmd->error = MMC_ERR_FAILED; + mv_sdio_finalize_request(sc); + return (-1); + } + + /* Write command argument. */ + MV_SDIO_WR4(sc, MV_SDIO_CMD_ARGL, cmd->arg & 0xffff); + MV_SDIO_WR4(sc, MV_SDIO_CMD_ARGH, cmd->arg >> 16); + + /* Determine response type. */ + if (cmd->flags & MMC_RSP_136) + cmdreg = MV_SDIO_CMD_RSP_136; + else if (cmd->flags & MMC_RSP_BUSY) + cmdreg = MV_SDIO_CMD_RSP_48_BUSY; + else if (cmd->flags & MMC_RSP_PRESENT) + cmdreg = MV_SDIO_CMD_RSP_48; + else { + /* No response. */ + cmdreg = MV_SDIO_CMD_RSP_NONE; + /* Enable host to detect unexpected response. */ + cmdreg |= MV_SDIO_CMD_UNEXPECTED_RSP; + sc->sc_irq_mask |= MV_SDIO_CMD_UNEXPECTED_RSP; + } + + /* Check command checksum if needed. */ + if (cmd->flags & MMC_RSP_CRC) + cmdreg |= MV_SDIO_CMD_CRC7; + /* Check command opcode if needed. */ + if (cmd->flags & MMC_RSP_OPCODE) + cmdreg |= MV_SDIO_CMD_INDEX_CHECK; + + /* Set commannd opcode. */ + cmdreg |= MV_SDIO_CMD_INDEX(cmd->opcode); + + /* Setup interrupts. */ + sc->sc_irq_mask = MV_SDIO_IRQ_CMD; + sc->sc_eirq_mask = MV_SDIO_EIRQ_ALL; + + /* Prepare data transfer. */ + if (cmd->data) { + cmdreg |= (MV_SDIO_CMD_DATA_PRESENT | MV_SDIO_CMD_DATA_CRC16); + if (mv_sdio_start_data(sc, cmd->data) < 0) { + cmd->error = MMC_ERR_FAILED; + printf("mv_sdio_start_data() failed!\n"); + mv_sdio_finalize_request(sc); + return (-1); + } + } + + /* Write command register. */ + MV_SDIO_WR4(sc, MV_SDIO_CMD, cmdreg); + + /* Clear interrupt status. */ + MV_SDIO_WR4(sc, MV_SDIO_IRQ_SR, ~MV_SDIO_IRQ_CARD_EVENT /*MV_SDIO_IRQ_ALL*/); + MV_SDIO_WR4(sc, MV_SDIO_EIRQ_SR, 0xffff /*MV_SDIO_EIRQ_ALL*/); + + /* Update interrupt/error interrupt enable registers. */ + MV_SDIO_WR4(sc, MV_SDIO_IRQ_EN, sc->sc_irq_mask); + MV_SDIO_WR4(sc, MV_SDIO_EIRQ_EN, sc->sc_eirq_mask); + + /* Do not complete request, interrupt handler will do this. */ + return (0); +} + +static void +mv_sdio_finish_command(struct mv_sdio_softc *sc) +{ + struct mmc_command *cmd; + struct mmc_data *data; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + cmd = sc->sc_curcmd; + data = cmd->data; + + /* Get response. */ + if (cmd->flags & MMC_RSP_PRESENT) { + if(cmd->flags & MMC_RSP_136) + /* 136-bit response. */ + mv_sdio_handle_136bit_resp(sc); + else + /* 48-bit response. */ + mv_sdio_handle_48bit_resp(sc, NULL); + } + + if (data) { + /* + * Disable command complete interrupt. It has already been + * handled. + */ + sc->sc_irq_mask &= ~MV_SDIO_IRQ_CMD; + + /* Enable XFER interrupt. */ + sc->sc_irq_mask |= MV_SDIO_IRQ_XFER; + + /* Check which data interrupts we need to activate. */ + if (sc->sc_use_dma) + /* DMA transaction. */ + sc->sc_irq_mask |= MV_SDIO_IRQ_DMA; + else if (data->flags & MMC_DATA_READ) + /* Read transaction in PIO mode. */ + sc->sc_irq_mask |= MV_SDIO_IRQ_RX_FULL; + else + /* Write transaction in PIO mode. */ + sc->sc_irq_mask |= MV_SDIO_IRQ_TX_EMPTY; + + /* Check if Auto-CMD12 interrupt will be needed. */ + if (sc->sc_req->stop) + sc->sc_irq_mask |= MV_SDIO_IRQ_AUTOCMD12; + + /* Update interrupt enable register. */ + MV_SDIO_WR4(sc, MV_SDIO_IRQ_EN, sc->sc_irq_mask); + } else { + /* We're done. Disable interrupts and finalize request. */ + mv_sdio_disable_intr(sc); + mv_sdio_finalize_request(sc); + } +} + +static int +mv_sdio_start_data(struct mv_sdio_softc *sc, struct mmc_data *data) +{ + struct mmc_command *stop; + uint32_t autocmd12reg, xfer, host_sr; + size_t blk_size, blk_count; + int retries; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + /* + * No transfer can be started when FIFO_EMPTY bit in MV_SDIO_HOST_SR + * is not set. This bit is sometimes not set instantly after XFER + * interrupt has been asserted. + */ + host_sr = MV_SDIO_RD4(sc, MV_SDIO_HOST_SR); + + retries = 10; + while (!(host_sr & MV_SDIO_HOST_SR_FIFO_EMPTY)) { + if (retries == 0) + return (-1); + retries--; + DELAY(MV_SDIO_FIFO_EMPTY_DELAY); + host_sr = MV_SDIO_RD4(sc, MV_SDIO_HOST_SR); + } + + /* Clear data offset. */ + sc->sc_data_offset = 0; + + /* + * Set block size. It can be less than or equal to MV_SDIO_BLOCK_SIZE + * bytes. + */ + blk_size = (data->len < MV_SDIO_BLOCK_SIZE) ? data->len : + MV_SDIO_BLOCK_SIZE; + MV_SDIO_WR4(sc, MV_SDIO_BLK_SIZE, blk_size); + + /* Set block count. */ + blk_count = (data->len + MV_SDIO_BLOCK_SIZE - 1) / MV_SDIO_BLOCK_SIZE; + MV_SDIO_WR4(sc, MV_SDIO_BLK_COUNT, blk_count); + + /* We want to initiate transfer by software. */ + xfer = MV_SDIO_XFER_SW_WR_EN; + + if (sc->sc_use_dma) { + /* Synchronize before DMA transfer. */ + if (data->flags & MMC_DATA_READ) + bus_dmamap_sync(sc->sc_dmatag, sc->sc_dmamap, + BUS_DMASYNC_PREREAD); + else { + memcpy(sc->sc_dmamem, data->data, data->len); + bus_dmamap_sync(sc->sc_dmatag, sc->sc_dmamap, + BUS_DMASYNC_PREWRITE); + } + + /* Write DMA buffer address register. */ + MV_SDIO_WR4(sc, MV_SDIO_DMA_ADDRL, sc->sc_physaddr & 0xffff); + MV_SDIO_WR4(sc, MV_SDIO_DMA_ADDRH, sc->sc_physaddr >> 16); + } else + /* Set PIO transfer mode. */ + xfer |= MV_SDIO_XFER_PIO; + + /* + * Prepare Auto-CMD12. This command is automatically sent to the card + * by the host controller to stop multiple-block data transaction. + */ + if (sc->sc_req->stop) { + stop = sc->sc_req->stop; + + /* Set Auto-CMD12 argument. */ + MV_SDIO_WR4(sc, MV_SDIO_AUTOCMD12_ARGL, stop->arg & 0xffff); + MV_SDIO_WR4(sc, MV_SDIO_AUTOCMD12_ARGH, stop->arg >> 16); + + /* Set Auto-CMD12 opcode. */ + autocmd12reg = MV_SDIO_AUTOCMD12_INDEX(stop->opcode); + + /* Check busy signal if needed. */ + if (stop->flags & MMC_RSP_BUSY) + autocmd12reg |= MV_SDIO_AUTOCMD12_BUSY_CHECK; + /* Check Auto-CMD12 index. */ + if (stop->flags & MMC_RSP_OPCODE) + autocmd12reg |= MV_SDIO_AUTOCMD12_INDEX_CHECK; + + MV_SDIO_WR4(sc, MV_SDIO_AUTOCMD12, autocmd12reg); + + xfer |= MV_SDIO_XFER_AUTOCMD12; + } + + /* Change data direction. */ + if (data->flags & MMC_DATA_READ) + xfer |= MV_SDIO_XFER_TO_HOST; + + /* Write transfer mode register. */ + MV_SDIO_WR4(sc, MV_SDIO_XFER, xfer); + + return (0); +} + +static void +mv_sdio_handle_136bit_resp(struct mv_sdio_softc *sc) +{ + struct mmc_command *cmd; + uint32_t resp[8]; + uint32_t base, extra; + int i, j, off; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + cmd = sc->sc_curcmd; + + /* Collect raw response from the controller. */ + for (i = 0; i < 8; i++) + resp[i] = MV_SDIO_RD4(sc, MV_SDIO_RSP(i)); + + /* Response passed to MMC bus is shifted by one byte. */ + extra = 0; + for (i = 0, j = 7; i < 4; i++, j -= 2) { + off = (i ? 0 : 2); + base = resp[j] | (resp[j - 1] << (16 - off)); + cmd->resp[3 - i] = (base << (6 + off)) + extra; + extra = base >> (26 - off); + } +} + +static void +mv_sdio_handle_48bit_resp(struct mv_sdio_softc *sc, struct mmc_command *stop) +{ + struct mmc_command *cmd; + uint32_t resp[3], word; + uint8_t *rp; + int i; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + if (stop == NULL) + cmd = sc->sc_curcmd; + else + cmd = stop; + + /* Collect raw response from the controller. */ + for (i = 0; i < 3; i++) { + if (stop == NULL) + resp[i] = MV_SDIO_RD4(sc, MV_SDIO_RSP(i)); + else + resp[i] = MV_SDIO_RD4(sc, MV_SDIO_AUTOCMD12_RSP(i)); + } + + /* Clear MMC bus response buffer. */ + bzero(&cmd->resp[0], 4 * sizeof(uint32_t)); + + /* + * Fill MMC bus response buffer. + */ + + rp = (uint8_t *)&cmd->resp[0]; + + /* Response bits [45:14] */ + word = (resp[1] & MV_SDIO_RSP48_BM16) | + ((resp[0] & MV_SDIO_RSP48_BM16) << 16); + + /* Response bits [15:14] and [13:8] */ + *rp++ = (resp[2] & MV_SDIO_RSP48_BM6) | + ((word & MV_SDIO_RSP48_BM2) << 6); + + /* Response bits [15:14] are already included. */ + word >>= 2; + + /* Response bits [45:16] */ + memcpy(rp, &word, sizeof(uint32_t)); +} + +static void +mv_sdio_intr(void *arg) +{ + struct mv_sdio_softc *sc; + uint32_t irq_stat, eirq_stat; + + sc = (struct mv_sdio_softc *)arg; +#if 0 + device_printf(sc->sc_dev,"intr 0x%04x intr_en 0x%04x hw_state 0x%04x\n", + MV_SDIO_RD4( sc, MV_SDIO_IRQ_SR ) , + MV_SDIO_RD4( sc, MV_SDIO_IRQ_EN ), + MV_SDIO_RD4( sc, MV_SDIO_HOST_SR )); +#endif + + + mtx_lock(&sc->sc_mtx); + + + + irq_stat = MV_SDIO_RD4(sc, MV_SDIO_IRQ_SR) & sc->sc_irq_mask; + eirq_stat = MV_SDIO_RD4(sc, MV_SDIO_EIRQ_SR) & sc->sc_eirq_mask; + + /* + * In case of error interrupt, interrupt cause will be identified by + * checking bits in error interrupt status register. + */ + irq_stat &= ~MV_SDIO_IRQ_ERR; + + /* Handle command interrupts. */ + if ((irq_stat & MV_SDIO_IRQS_CMD) || + (eirq_stat & MV_SDIO_EIRQS_CMD)) { + MV_SDIO_WR4(sc, MV_SDIO_IRQ_SR, irq_stat); + MV_SDIO_WR4(sc, MV_SDIO_EIRQ_SR, eirq_stat); + mv_sdio_cmd_intr(sc, irq_stat, eirq_stat); + irq_stat &= ~MV_SDIO_IRQS_CMD; + eirq_stat &= ~MV_SDIO_EIRQS_CMD; + } + + /* Handle data interrupts. */ + if ((irq_stat & MV_SDIO_IRQS_DATA) || + (eirq_stat & MV_SDIO_EIRQS_DATA)) { + MV_SDIO_WR4(sc, MV_SDIO_IRQ_SR, irq_stat); + MV_SDIO_WR4(sc, MV_SDIO_EIRQ_SR, eirq_stat); + mv_sdio_data_intr(sc, irq_stat, eirq_stat); + irq_stat &= ~MV_SDIO_IRQS_DATA; + eirq_stat &= ~MV_SDIO_EIRQS_DATA; + } + + /* Handle unexpected interrupts. */ + if (irq_stat) { + device_printf(sc->sc_dev, "Unexpected interrupt(s)! " + "IRQ SR = 0x%08x\n", irq_stat); + /* Clear interrupt status. */ + MV_SDIO_WR4(sc, MV_SDIO_IRQ_SR, irq_stat); + } + if (eirq_stat) { + device_printf(sc->sc_dev, "Unexpected error interrupt(s)! " + "EIRQ SR = 0x%08x\n", eirq_stat); + /* Clear error interrupt status. */ + MV_SDIO_WR4(sc, MV_SDIO_EIRQ_SR, eirq_stat); + } + + mtx_unlock(&sc->sc_mtx); +} + +static void +mv_sdio_cmd_intr(struct mv_sdio_softc *sc, uint32_t irq, uint32_t eirq) +{ + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + if (!sc->sc_curcmd) { + device_printf(sc->sc_dev, "Got command interrupt, but there " + "is no active command!\n"); + return; + } + + /* Handle unexpected response error. */ + if (irq & MV_SDIO_IRQ_UNEXPECTED_RSP) { + sc->sc_curcmd->error = MMC_ERR_FAILED; + device_printf(sc->sc_dev, "Unexpected response!\n"); + } + + /* Handle errors. */ + if (eirq & MV_SDIO_EIRQ_CMD_TMO) { + sc->sc_curcmd->error = MMC_ERR_TIMEOUT; + device_printf(sc->sc_dev, "Error - command %d timeout!\n", + sc->sc_curcmd->opcode); + } else if (eirq & MV_SDIO_EIRQ_CMD_CRC7) { + sc->sc_curcmd->error = MMC_ERR_BADCRC; + device_printf(sc->sc_dev, "Error - bad command %d " + "checksum!\n", sc->sc_curcmd->opcode); + } else if (eirq) { + sc->sc_curcmd->error = MMC_ERR_FAILED; + device_printf(sc->sc_dev, "Command %d error!\n", + sc->sc_curcmd->opcode); + } + + if (sc->sc_curcmd->error != MMC_ERR_NONE) { + /* Error. Disable interrupts and finalize request. */ + mv_sdio_disable_intr(sc); + mv_sdio_finalize_request(sc); + return; + } + + if (irq & MV_SDIO_IRQ_CMD) + mv_sdio_finish_command(sc); +} + +static void +mv_sdio_data_intr(struct mv_sdio_softc *sc, uint32_t irq, uint32_t eirq) +{ + struct mmc_command *stop; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + if (!sc->sc_curcmd) { + device_printf(sc->sc_dev, "Got data interrupt, but there is " + "no active command.\n"); + return; + } + if ((!sc->sc_curcmd->data) && ((sc->sc_curcmd->flags & + MMC_RSP_BUSY) == 0)) { + device_printf(sc->sc_dev, "Got data interrupt, but there is " + "no active data transaction.n\n"); + sc->sc_curcmd->error = MMC_ERR_FAILED; + return; + } + + /* Handle errors. */ + if(eirq & MV_SDIO_EIRQ_DATA_TMO) { + sc->sc_curcmd->error = MMC_ERR_TIMEOUT; + device_printf(sc->sc_dev, "Data %s timeout!\n", + (sc->sc_curcmd->data->flags & MMC_DATA_READ) ? "read" : + "write"); + } else if (eirq & (MV_SDIO_EIRQ_DATA_CRC16 | + MV_SDIO_EIRQ_DATA_ENDBIT)) { + sc->sc_curcmd->error = MMC_ERR_BADCRC; + device_printf(sc->sc_dev, "Bad data checksum!\n"); + } else if (eirq) { + sc->sc_curcmd->error = MMC_ERR_FAILED; + device_printf(sc->sc_dev, "Data error!: 0x%04X \n", + eirq); + + if( 0 != ( eirq & MV_SDIO_EIRQ_CRC_STAT ) ) + { + device_printf(sc->sc_dev, "MV_SDIO_EIRQ_CRC_STAT\n"); + } + } + + /* Handle Auto-CMD12 error. */ + if (eirq & MV_SDIO_EIRQ_AUTOCMD12) { + sc->sc_req->stop->error = MMC_ERR_FAILED; + sc->sc_curcmd->error = MMC_ERR_FAILED; + device_printf(sc->sc_dev, "Auto-CMD12 error!\n"); + } + + if (sc->sc_curcmd->error != MMC_ERR_NONE) { + /* Error. Disable interrupts and finalize request. */ + mv_sdio_disable_intr(sc); + mv_sdio_finalize_request(sc); + return; + } + + /* Handle PIO interrupt. */ + if (irq & (MV_SDIO_IRQ_TX_EMPTY | MV_SDIO_IRQ_RX_FULL)) + mv_sdio_transfer_pio(sc); + + /* Handle DMA interrupt. */ + if (irq & (MV_SDIO_IRQ_DMA)) { + /* Synchronize DMA buffer. */ + if (MV_SDIO_RD4(sc, MV_SDIO_XFER) & MV_SDIO_XFER_TO_HOST) { + bus_dmamap_sync(sc->sc_dmatag, sc->sc_dmamap, + BUS_DMASYNC_POSTWRITE); + memcpy(sc->sc_curcmd->data->data, sc->sc_dmamem, + sc->sc_curcmd->data->len); + } else + bus_dmamap_sync(sc->sc_dmatag, sc->sc_dmamap, + BUS_DMASYNC_POSTREAD); + + /* Disable DMA interrupt. */ + sc->sc_irq_mask &= ~MV_SDIO_IRQ_DMA; + MV_SDIO_WR4(sc, MV_SDIO_IRQ_EN, sc->sc_irq_mask); + } + + /* Handle Auto-CMD12 interrupt. */ + if (irq & (MV_SDIO_IRQ_AUTOCMD12)) { + stop = sc->sc_req->stop; + /* Get 48-bit response. */ + mv_sdio_handle_48bit_resp(sc, stop); + + /* Disable Auto-CMD12 interrupt. */ + sc->sc_irq_mask &= ~MV_SDIO_IRQ_AUTOCMD12; + MV_SDIO_WR4(sc, MV_SDIO_IRQ_EN, sc->sc_irq_mask); + } + + /* Transfer finished. Disable interrupts and finalize request. */ + if (irq & (MV_SDIO_IRQ_XFER)) { + mv_sdio_disable_intr(sc); + mv_sdio_finalize_request(sc); + } +} + +static void +mv_sdio_disable_intr(struct mv_sdio_softc *sc) +{ + + /* Disable interrupts that were enabled. */ + sc->sc_irq_mask &= ~(sc->sc_irq_mask); + sc->sc_eirq_mask &= ~(sc->sc_eirq_mask); + MV_SDIO_WR4(sc, MV_SDIO_IRQ_EN, sc->sc_irq_mask); + MV_SDIO_WR4(sc, MV_SDIO_EIRQ_EN, sc->sc_eirq_mask); +} + +static void +mv_sdio_card_task(void *arg, int pending) +{ + struct mv_sdio_softc *sc; + + int device_probe_and_attach_ret_val = 0; + + sc = (struct mv_sdio_softc *)arg; + + mtx_lock(&sc->sc_mtx); + + if (sc->sc_card_present) { + if (sc->sc_child) { + mtx_unlock(&sc->sc_mtx); + return; + } + + /* Initialize host controller's registers. */ + mv_sdio_init(sc->sc_dev); + + sc->sc_child = device_add_child(sc->sc_dev, "mmc", -1); + if (sc->sc_child == NULL) { + device_printf(sc->sc_dev, "Could not add MMC bus!\n"); + mtx_unlock(&sc->sc_mtx); + return; + } + + /* Initialize host structure for MMC bus. */ + mv_sdio_init_host(sc); + + device_set_ivars(sc->sc_child, &sc->sc_host); + + mtx_unlock(&sc->sc_mtx); + + device_probe_and_attach_ret_val = device_probe_and_attach(sc->sc_child); + + if( 0 != device_probe_and_attach_ret_val ) { + device_printf(sc->sc_dev, "MMC bus failed on probe " + "and attach! %i\n",device_probe_and_attach_ret_val); + device_delete_child(sc->sc_dev, sc->sc_child); + sc->sc_child = NULL; + } + } else { + if (sc->sc_child == NULL) { + mtx_unlock(&sc->sc_mtx); + return; + } + + mtx_unlock(&sc->sc_mtx); + if (device_delete_child(sc->sc_dev, sc->sc_child) != 0) { + device_printf(sc->sc_dev, "Could not delete MMC " + "bus!\n"); + } + sc->sc_child = NULL; + } +} + +static uint32_t +mv_sdio_read_fifo(struct mv_sdio_softc *sc) +{ + uint32_t data; + device_printf(sc->sc_dev, "This is not tested, yet MV_SDIO_FIFO not ensured\n "); + + while (!(MV_SDIO_RD4(sc, MV_SDIO_IRQ_SR) & MV_SDIO_IRQ_RX_FULL)); + data = MV_SDIO_RD4(sc, MV_SDIO_FIFO); + while (!(MV_SDIO_RD4(sc, MV_SDIO_IRQ_SR) & MV_SDIO_IRQ_RX_FULL)); + data |= (MV_SDIO_RD4(sc, MV_SDIO_FIFO) << 16); + return data; +} + +static void +mv_sdio_write_fifo(struct mv_sdio_softc *sc, uint32_t val) +{ + while (!(MV_SDIO_RD4(sc, MV_SDIO_IRQ_SR) & MV_SDIO_IRQ_TX_EMPTY)); + MV_SDIO_WR4(sc, MV_SDIO_FIFO, val & 0xffff); + while (!(MV_SDIO_RD4(sc, MV_SDIO_IRQ_SR) & MV_SDIO_IRQ_TX_EMPTY)); + MV_SDIO_WR4(sc, MV_SDIO_FIFO, val >> 16); +} + +static void +mv_sdio_transfer_pio(struct mv_sdio_softc *sc) +{ + struct mmc_command *cmd; + + cmd = sc->sc_curcmd; + + if (cmd->data->flags & MMC_DATA_READ) { + while (MV_SDIO_RD4(sc, MV_SDIO_IRQ_SR) & + MV_SDIO_IRQ_RX_FULL) { + mv_sdio_read_block_pio(sc); + /* + * Assert delay after each block transfer to meet read + * access timing constraint. + */ + DELAY(MV_SDIO_RD_DELAY); + if (sc->sc_data_offset >= cmd->data->len) + break; + } + /* All blocks read in PIO mode. Disable interrupt. */ + sc->sc_irq_mask &= ~MV_SDIO_IRQ_RX_FULL; + MV_SDIO_WR4(sc, MV_SDIO_IRQ_EN, sc->sc_irq_mask); + } else { + while (MV_SDIO_RD4(sc, MV_SDIO_IRQ_SR) & + MV_SDIO_IRQ_TX_EMPTY) { + mv_sdio_write_block_pio(sc); + /* Wait while card is programming the memory. */ + while ((MV_SDIO_RD4(sc, MV_SDIO_HOST_SR) & + MV_SDIO_HOST_SR_CARD_BUSY)); + /* + * Assert delay after each block transfer to meet + * write access timing constraint. + */ + DELAY(MV_SDIO_WR_DELAY); + + if (sc->sc_data_offset >= cmd->data->len) + break; + } + /* All blocks written in PIO mode. Disable interrupt. */ + sc->sc_irq_mask &= ~MV_SDIO_IRQ_TX_EMPTY; + MV_SDIO_WR4(sc, MV_SDIO_IRQ_EN, sc->sc_irq_mask); + } +} + +static void +mv_sdio_read_block_pio(struct mv_sdio_softc *sc) +{ + uint32_t data; + char *buffer; + size_t left; + + buffer = sc->sc_curcmd->data->data; + buffer += sc->sc_data_offset; + /* Transfer one block at a time. */ + left = min(MV_SDIO_BLOCK_SIZE, sc->sc_curcmd->data->len - + sc->sc_data_offset); + sc->sc_data_offset += left; + + /* Handle unaligned and aligned buffer cases. */ + if ((intptr_t)buffer & 3) { + while (left > 3) { + data = mv_sdio_read_fifo(sc); + buffer[0] = data; + buffer[1] = (data >> 8); + buffer[2] = (data >> 16); + buffer[3] = (data >> 24); + buffer += 4; + left -= 4; + } + } else { + while (left > 3) { + data = mv_sdio_read_fifo(sc); + *((uint32_t *)buffer) = data; + buffer += 4; + left -= 4; + } + } + /* Handle uneven size case. */ + if (left > 0) { + data = mv_sdio_read_fifo(sc); + while (left > 0) { + *(buffer++) = data; + data >>= 8; + left--; + } + } +} + +static void +mv_sdio_write_block_pio(struct mv_sdio_softc *sc) +{ + uint32_t data = 0; + char *buffer; + size_t left; + + buffer = sc->sc_curcmd->data->data; + buffer += sc->sc_data_offset; + /* Transfer one block at a time. */ + left = min(MV_SDIO_BLOCK_SIZE, sc->sc_curcmd->data->len - + sc->sc_data_offset); + sc->sc_data_offset += left; + + /* Handle unaligned and aligned buffer cases. */ + if ((intptr_t)buffer & 3) { + while (left > 3) { + data = buffer[0] + + (buffer[1] << 8) + + (buffer[2] << 16) + + (buffer[3] << 24); + left -= 4; + buffer += 4; + mv_sdio_write_fifo(sc, data); + } + } else { + while (left > 3) { + data = *((uint32_t *)buffer); + left -= 4; + buffer += 4; + mv_sdio_write_fifo(sc, data); + } + } + /* Handle uneven size case. */ + if (left > 0) { + data = 0; + while (left > 0) { + data <<= 8; + data += *(buffer++); + left--; + } + mv_sdio_write_fifo(sc, data); + } +} + +static int +mv_sdio_read_ivar(device_t dev, device_t child, int index, uintptr_t *result) +{ + struct mv_sdio_softc *sc; + struct mmc_host *host; + + sc = device_get_softc(dev); + host = device_get_ivars(child); + + switch (index) { + case MMCBR_IVAR_BUS_MODE: + *(int *)result = host->ios.bus_mode; + break; + case MMCBR_IVAR_BUS_WIDTH: + *(int *)result = host->ios.bus_width; + break; + case MMCBR_IVAR_CHIP_SELECT: + *(int *)result = host->ios.chip_select; + break; + case MMCBR_IVAR_CLOCK: + *(int *)result = host->ios.clock; + break; + case MMCBR_IVAR_F_MIN: + *(int *)result = host->f_min; + break; + case MMCBR_IVAR_F_MAX: + *(int *)result = host->f_max; + break; + case MMCBR_IVAR_HOST_OCR: + *(int *)result = host->host_ocr; + break; + case MMCBR_IVAR_MODE: + *(int *)result = host->mode; + break; + case MMCBR_IVAR_OCR: + *(int *)result = host->ocr; + break; + case MMCBR_IVAR_POWER_MODE: + *(int *)result = host->ios.power_mode; + break; + case MMCBR_IVAR_VDD: + *(int *)result = host->ios.vdd; + break; + case MMCBR_IVAR_CAPS: + *(int *)result = host->caps; + break; + case MMCBR_IVAR_TIMING: + *(int *)result = host->ios.timing; + break; + case MMCBR_IVAR_MAX_DATA: + mtx_lock(&sc->sc_mtx); + /* Return maximum number of blocks the driver can handle. */ + if (sc->sc_use_dma) + *(int *)result = (sc->sc_dma_size / + MV_SDIO_BLOCK_SIZE); + else + *(int *)result = MV_SDIO_BLOCKS_MAX; + mtx_unlock(&sc->sc_mtx); + break; + default: + return (EINVAL); + } + + return (0); +} + +static int +mv_sdio_write_ivar(device_t dev, device_t child, int index, uintptr_t value) +{ + struct mmc_host *host; + + host = device_get_ivars(child); + + switch (index) { + case MMCBR_IVAR_BUS_MODE: + host->ios.bus_mode = value; + break; + case MMCBR_IVAR_BUS_WIDTH: + host->ios.bus_width = value; + break; + case MMCBR_IVAR_CHIP_SELECT: + host->ios.chip_select = value; + break; + case MMCBR_IVAR_CLOCK: + host->ios.clock = value; + break; + case MMCBR_IVAR_MODE: + host->mode = value; + break; + case MMCBR_IVAR_OCR: + host->ocr = value; + break; + case MMCBR_IVAR_POWER_MODE: + host->ios.power_mode = value; + break; + case MMCBR_IVAR_VDD: + host->ios.vdd = value; + break; + case MMCBR_IVAR_TIMING: + host->ios.timing = value; + break; + case MMCBR_IVAR_CAPS: + case MMCBR_IVAR_HOST_OCR: + case MMCBR_IVAR_F_MIN: + case MMCBR_IVAR_F_MAX: + case MMCBR_IVAR_MAX_DATA: + default: + /* Instance variable not writable. */ + return (EINVAL); + } + + return (0); +} + diff --git a/sys/arm/mv/mv_sdio.h b/sys/arm/mv/mv_sdio.h new file mode 100644 index 0000000..b54b59d --- /dev/null +++ b/sys/arm/mv/mv_sdio.h @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2008 Marvell Semiconductors, All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef _MVSDMMC_INCLUDE +#define _MVSDMMC_INCLUDE + + +#define MVSDMMC_DMA_SIZE 65536 + + + +/* + * The base MMC clock rate + */ + +#define MVSDMMC_CLOCKRATE_MIN 100000 +#define MVSDMMC_CLOCKRATE_MAX 50000000 + +#define MVSDMMC_BASE_FAST_CLOCK 200000000 + + +/* + * SDIO register + */ + +#define MV_SDIO_DMA_ADDRL 0x000 +#define MV_SDIO_DMA_ADDRH 0x004 +#define MV_SDIO_BLK_SIZE 0x008 +#define MV_SDIO_BLK_COUNT 0x00c +#define MV_SDIO_CMD 0x01c +#define MV_SDIO_CMD_ARGL 0x010 +#define MV_SDIO_CMD_ARGH 0x014 +#define MV_SDIO_XFER 0x018 +#define MV_SDIO_HOST_SR 0x048 +#define MV_SDIO_HOST_CR 0x050 +#define MV_SDIO_SW_RESET 0x05c +#define MV_SDIO_IRQ_SR 0x060 +#define MV_SDIO_EIRQ_SR 0x064 +#define MV_SDIO_IRQ_SR_EN 0x068 +#define MV_SDIO_EIRQ_SR_EN 0x06c +#define MV_SDIO_IRQ_EN 0x070 +#define MV_SDIO_EIRQ_EN 0x074 +#define MV_SDIO_AUTOCMD12_ARGL 0x084 +#define MV_SDIO_AUTOCMD12_ARGH 0x088 +#define MV_SDIO_AUTOCMD12 0x08c +#define MV_SDIO_CLK_DIV 0x128 +#define MV_SDIO_FIFO 0xa2100 /* FIXME!!! */ + +#define MV_SDIO_RSP(i) (0x020 + ((i)<<2)) +#define MV_SDIO_AUTOCMD12_RSP(i) (0x090 + ((i)<<2)) + +/* + * SDIO Status-Register + */ +#define MV_SDIO_HOST_SR_CARD_BUSY (1<<1) +#define MV_SDIO_HOST_SR_FIFO_EMPTY (1<<13) + + + +/* + * SDIO_CMD + */ +#define MV_SDIO_CMD_RSP_NONE (0 << 0) +#define MV_SDIO_CMD_RSP_136 (1 << 0) +#define MV_SDIO_CMD_RSP_48 (2 << 0) +#define MV_SDIO_CMD_RSP_48_BUSY (3 << 0) +#define MV_SDIO_CMD_DATA_CRC16 (1<<2) +#define MV_SDIO_CMD_CRC7 (1<<3) +#define MV_SDIO_CMD_INDEX_CHECK (1<<4) +#define MV_SDIO_CMD_DATA_PRESENT (1<<5) +#define MV_SDIO_CMD_UNEXPECTED_RSP (1<<7) +#define MV_SDIO_CMD_INDEX(x) ( (x) << 8 ) + + +/* + * SDIO_XFER_MODE + */ +#define MV_SDIO_XFER_STOP_CLK (1 << 5) +#define MV_SDIO_XFER_TO_HOST (1 << 4) +#define MV_SDIO_XFER_PIO (1 << 3) +#define MV_SDIO_XFER_AUTOCMD12 (1 << 2) +#define MV_SDIO_XFER_SW_WR_EN (1 << 1) + +/* + * SDIO_HOST_CTRL + */ +#define MV_SDIO_HOST_CR_PUSHPULL (1 << 0) +#define MV_SDIO_HOST_CR_MMC (3 << 1) +#define MV_SDIO_HOST_CR_BE (1 << 3) +#define MV_SDIO_HOST_CR_4BIT (1 << 9) +#define MV_SDIO_HOST_CR_HIGHSPEED (1 << 10) + +#define MV_SDIO_HOST_CR_TMOVAL(x) ((x) << 11) +#define MV_SDIO_HOST_CR_TMO ( 1 << 15 ) + +/* + * NORmal status bits + */ + + +#define MV_SDIO_IRQ_ERR (1<<15) +#define MV_SDIO_IRQ_UNEXPECTED_RSP (1<<14) +#define MV_SDIO_IRQ_AUTOCMD12 (1<<13) +#define MV_SDIO_IRQ_SUSPENSE_ON_IRQ_EN (1<<12) +#define MV_SDIO_IRQ_IMB_FIFO_WORD_AVAIL (1<<11) +#define MV_SDIO_IRQ_IMB_FIFO_WORD_FILLED (1<<10) +#define MV_SDIO_IRQ_READ_WAIT (1<<9) +#define MV_SDIO_IRQ_CARD_EVENT (1<<8) +#define MV_SDIO_IRQ_RX_FULL (1<<5) +#define MV_SDIO_IRQ_TX_EMPTY (1<<4) +#define MV_SDIO_IRQ_DMA (1<<3) +#define MV_SDIO_IRQ_BLOCK_GAP (1<<2) +#define MV_SDIO_IRQ_XFER (1<<1) +#define MV_SDIO_IRQ_CMD (1<<0) + +#define MV_SDIO_IRQ_ALL (MV_SDIO_IRQ_CMD | MV_SDIO_IRQ_XFER | MV_SDIO_IRQ_BLOCK_GAP | MV_SDIO_IRQ_DMA | MV_SDIO_IRQ_RX_FULL | MV_SDIO_IRQ_TX_EMPTY | MV_SDIO_IRQ_CARD_EVENT | MV_SDIO_IRQ_READ_WAIT | MV_SDIO_IRQ_IMB_FIFO_WORD_FILLED | MV_SDIO_IRQ_IMB_FIFO_WORD_AVAIL | MV_SDIO_IRQ_SUSPENSE_ON_IRQ_EN | MV_SDIO_IRQ_AUTOCMD12 | MV_SDIO_IRQ_UNEXPECTED_RSP | MV_SDIO_IRQ_ERR ) + +//#define MV_SDIO_IRQ_SR + + +/* + * ERR status bits + */ +#define MV_SDIO_EIRQ_CRC_STAT (1<<14) +#define MV_SDIO_EIRQ_CRC_STARTBIT (1<<13) +#define MV_SDIO_EIRQ_CRC_ENDBIT (1<<12) +#define MV_SDIO_EIRQ_RSP_TBIT (1<<11) +#define MV_SDIO_EIRQ_XFER_SIZE (1<<10) +#define MV_SDIO_EIRQ_CMD_STARTBIT (1<<9) +#define MV_SDIO_EIRQ_AUTOCMD12 (1<<8) +#define MV_SDIO_EIRQ_DATA_ENDBIT (1<<6) +#define MV_SDIO_EIRQ_DATA_CRC16 (1<<5) +#define MV_SDIO_EIRQ_DATA_TMO (1<<4) +#define MV_SDIO_EIRQ_CMD_INDEX (1<<3) +#define MV_SDIO_EIRQ_CMD_ENDBIT (1<<2) +#define MV_SDIO_EIRQ_CMD_CRC7 (1<<1) +#define MV_SDIO_EIRQ_CMD_TMO (1<<0) + +#define MV_SDIO_EIRQ_ALL (MV_SDIO_EIRQ_CMD_TMO | \ + MV_SDIO_EIRQ_CMD_CRC7 | \ + MV_SDIO_EIRQ_CMD_ENDBIT | \ + MV_SDIO_EIRQ_CMD_INDEX | \ + MV_SDIO_EIRQ_DATA_TMO | \ + MV_SDIO_EIRQ_DATA_CRC16 | \ + MV_SDIO_EIRQ_DATA_ENDBIT | \ + MV_SDIO_EIRQ_AUTOCMD12 | \ + MV_SDIO_EIRQ_CMD_STARTBIT |\ + MV_SDIO_EIRQ_XFER_SIZE |\ + MV_SDIO_EIRQ_RSP_TBIT |\ + MV_SDIO_EIRQ_CRC_ENDBIT |\ + MV_SDIO_EIRQ_CRC_STARTBIT |\ + MV_SDIO_EIRQ_CRC_STAT) + +/* AUTOCMD12 register values */ +#define MV_SDIO_AUTOCMD12_BUSY_CHECK (1<<0) +#define MV_SDIO_AUTOCMD12_INDEX_CHECK (1<<1) +#define MV_SDIO_AUTOCMD12_INDEX(x) (x<<8) + +/* Software reset register */ +#define MV_SDIO_SW_RESET_ALL (1<<8) + +/* */ +#define MV_SDIO_SIG_CD 1 +#define MV_SDIO_SIG_WP 2 + +#endif /* _MVSDMMC_INCLUDE */ + diff --git a/sys/boot/uboot/common/main.c b/sys/boot/uboot/common/main.c index 82c86b2..0a5b368 100644 --- a/sys/boot/uboot/common/main.c +++ b/sys/boot/uboot/common/main.c @@ -122,6 +122,7 @@ main(void) struct api_signature *sig = NULL; int i; struct open_file f; + char *ub_currdev; if (!api_search_sig(&sig)) return (-1); @@ -166,6 +167,7 @@ main(void) printf("(%s, %s)\n", bootprog_maker, bootprog_date); meminfo(); + ub_currdev = ub_env_get("currdev"); /* * March through the device switch probing for things. */ @@ -198,8 +200,13 @@ main(void) if (devsw[i] == NULL) panic("No boot device found!"); - env_setenv("currdev", EV_VOLATILE, uboot_fmtdev(&currdev), - uboot_setcurrdev, env_nounset); + if (ub_currdev) { + env_setenv("currdev", EV_VOLATILE, ub_currdev, + uboot_setcurrdev, env_nounset); + } else { + env_setenv("currdev", EV_VOLATILE, uboot_fmtdev(&currdev), + uboot_setcurrdev, env_nounset); + } env_setenv("loaddev", EV_VOLATILE, uboot_fmtdev(&currdev), env_noset, env_nounset); diff --git a/sys/dev/mmc/mmc.c b/sys/dev/mmc/mmc.c index f101e65..5016828 100644 --- a/sys/dev/mmc/mmc.c +++ b/sys/dev/mmc/mmc.c @@ -67,15 +67,32 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include + #include "mmcbr_if.h" #include "mmcbus_if.h" +/* CIS structure of SDIO card */ +struct sdio_function { + int number; + uint8_t cis1_major; + uint8_t cis1_minor; + uint16_t manufacturer; + uint16_t product; + uint16_t fn0_blksize; + uint8_t max_tran_speed; + STAILQ_ENTRY(sdio_function) sdiof_list; +}; + struct mmc_softc { device_t dev; struct mtx sc_mtx; struct intr_config_hook config_intrhook; device_t owner; uint32_t last_rca; + uint8_t sdio_nfunc; + struct sdio_function sdio_func0; + STAILQ_HEAD(, sdio_function) sdiof_head; }; /* @@ -96,11 +113,13 @@ struct mmc_ivars { u_char read_only; /* True when the device is read-only */ u_char bus_width; /* Bus width to use */ u_char timing; /* Bus timing support */ + uint8_t mem_present; /* Is memory present */ u_char high_cap; /* High Capacity card (block addressed) */ uint32_t sec_count; /* Card capacity in 512byte blocks */ uint32_t tran_speed; /* Max speed in normal mode */ uint32_t hs_tran_speed; /* Max speed in high speed mode */ uint32_t erase_sector; /* Card native erase sector size */ + uint8_t sdio_numfunc; /* Number of IO functions */ char card_id_string[64];/* Formatted CID info (serial, MFG, etc) */ }; @@ -159,10 +178,15 @@ static uint32_t mmc_get_bits(uint32_t *bits, int bit_len, int start, int size); static int mmc_highest_voltage(uint32_t ocr); static void mmc_idle_cards(struct mmc_softc *sc); +static int mmc_io_func_enable(struct mmc_softc *sc, uint32_t fn); +static int mmc_io_rw_direct(struct mmc_softc *sc, int wr, uint32_t fn, + uint32_t adr, uint8_t *data); static void mmc_ms_delay(int ms); static void mmc_log_card(device_t dev, struct mmc_ivars *ivar, int newcard); static void mmc_power_down(struct mmc_softc *sc); static void mmc_power_up(struct mmc_softc *sc); +static int mmc_probe_sdio(struct mmc_softc *sc, uint32_t ocr, uint32_t *rocr, + uint8_t *nfunc, uint8_t *mem_present); static void mmc_rescan_cards(struct mmc_softc *sc); static void mmc_scan(struct mmc_softc *sc); static int mmc_sd_switch(struct mmc_softc *sc, uint8_t mode, uint8_t grp, @@ -220,6 +244,8 @@ mmc_attach(device_t dev) sc->dev = dev; MMC_LOCK_INIT(sc); + STAILQ_INIT(&sc->sdiof_head); + /* We'll probe and attach our children later, but before / mount */ sc->config_intrhook.ich_func = mmc_delayed_attach; sc->config_intrhook.ich_arg = sc; @@ -470,6 +496,7 @@ mmc_wait_for_command(struct mmc_softc *sc, uint32_t opcode, return (0); } +/* CMD0 */ static void mmc_idle_cards(struct mmc_softc *sc) { @@ -494,6 +521,7 @@ mmc_idle_cards(struct mmc_softc *sc) mmc_ms_delay(1); } +/* CMD41 -> CMD55 */ static int mmc_send_app_op_cond(struct mmc_softc *sc, uint32_t ocr, uint32_t *rocr) { @@ -521,6 +549,7 @@ mmc_send_app_op_cond(struct mmc_softc *sc, uint32_t ocr, uint32_t *rocr) return (err); } +/* CMD1 */ static int mmc_send_op_cond(struct mmc_softc *sc, uint32_t ocr, uint32_t *rocr) { @@ -548,6 +577,7 @@ mmc_send_op_cond(struct mmc_softc *sc, uint32_t ocr, uint32_t *rocr) return (err); } +/* CMD8 */ static int mmc_send_if_cond(struct mmc_softc *sc, uint8_t vhs) { @@ -600,6 +630,7 @@ mmc_power_down(struct mmc_softc *sc) mmcbr_update_ios(dev); } +/* CMD7 */ static int mmc_select_card(struct mmc_softc *sc, uint16_t rca) { @@ -1042,6 +1073,7 @@ mmc_app_decode_sd_status(uint32_t *raw_sd_status, sd_status->erase_offset = mmc_get_bits(raw_sd_status, 512, 400, 2); } +/* CMD2 */ static int mmc_all_send_cid(struct mmc_softc *sc, uint32_t *rawcid) { @@ -1162,6 +1194,7 @@ mmc_set_relative_addr(struct mmc_softc *sc, uint16_t resp) return (err); } +/* CMD3 */ static int mmc_send_relative_addr(struct mmc_softc *sc, uint32_t *resp) { @@ -1177,6 +1210,7 @@ mmc_send_relative_addr(struct mmc_softc *sc, uint32_t *resp) return (err); } +/* CMD13 */ static int mmc_send_status(struct mmc_softc *sc, uint16_t rca, uint32_t *status) { @@ -1223,6 +1257,287 @@ mmc_log_card(device_t dev, struct mmc_ivars *ivar, int newcard) ivar->read_only ? ", read-only" : ""); } +static int +mmc_io_func_enable(struct mmc_softc *sc, uint32_t fn) +{ + int err, i; + uint8_t funcs; + + /* XXX Check if function number is valid */ + + err = mmc_io_rw_direct(sc, 0, 0, SD_IO_CCCR_FN_READY, &funcs); + if (err != MMC_ERR_NONE) { + device_printf(sc->dev, "Error reading SDIO func ready %d\n", err); + return (err); + } + funcs |= 1 << fn; + err = mmc_io_rw_direct(sc, 1, 0, SD_IO_CCCR_FN_ENABLE, &funcs); + if (err != MMC_ERR_NONE) { + device_printf(sc->dev, "Error writing SDIO func enable %d\n", err); + return (err); + } + + funcs = 0; + for(i=0; i < 10; i++) { + err = mmc_io_rw_direct(sc, 0, 0, SD_IO_CCCR_FN_READY, &funcs); + if (err != MMC_ERR_NONE) { + device_printf(sc->dev, "Error reading SDIO func ready %d\n", err); + return (err); + } + + if (funcs & (1 << fn)) + return 0; + pause("mmc_io_func_enable", 100); + } + + device_printf(sc->dev, "Cannot enable function %d!\n", fn); + return 1; +} + +static int +mmc_io_rw_direct(struct mmc_softc *sc, int wr, uint32_t fn, uint32_t adr, + uint8_t *data) +{ + struct mmc_command cmd; + int err; + + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = SD_IO_RW_DIRECT; + cmd.arg = SD_IO_RW_FUNC(fn) | SD_IO_RW_ADR(adr); + if (wr) + cmd.arg |= SD_IO_RW_WR | SD_IO_RW_RAW | SD_IO_RW_DAT(*data); + cmd.flags = MMC_RSP_R5 | MMC_CMD_AC; + cmd.data = NULL; + + err = mmc_wait_for_cmd(sc, &cmd, CMD_RETRIES); + if (err) + return (err); + if (cmd.error) + return (cmd.error); + + if (cmd.resp[0] & R5_COM_CRC_ERROR) + return (MMC_ERR_BADCRC); + if (cmd.resp[0] & (R5_ILLEGAL_COMMAND | R5_FUNCTION_NUMBER)) + return (MMC_ERR_INVALID); + if (cmd.resp[0] & R5_OUT_OF_RANGE) + return (MMC_ERR_FAILED); + + /* Just for information... */ + if (R5_IO_CURRENT_STATE(cmd.resp[0]) != 1) + printf("!!! SDIO state %d\n", R5_IO_CURRENT_STATE(cmd.resp[0])); + + *data = (uint8_t) (cmd.resp[0] & 0xff); + return (MMC_ERR_NONE); +} + +/* CMD52 */ +static uint8_t +mmc_io_read_1(struct mmc_softc *sc, uint32_t fn, uint32_t adr) +{ + int err; + uint8_t val = 0; + + err = mmc_io_rw_direct(sc, 0, fn, adr, &val); + if (err) { + device_printf(sc->dev, "Err reading FN %d addr 0x%08X: %d", + fn, adr, err); + return (0xff); + } + return val; +} + +/* + * Parse Card Information Structure of the SDIO card. + * Both Function 0 CIS and Function 1-7 CIS are supported. + */ +static int +mmc_io_parse_cis(struct mmc_softc *sc, uint8_t func, uint32_t cisptr, struct sdio_function *sdio_func) +{ + uint32_t tmp; + + uint8_t tuple_id, tuple_len, func_id; + uint32_t addr, maninfo_p; + + char *cis1_info[4]; + int start, i, ch, count; + char cis1_info_buf[256]; + + sdio_func->number = func; + + cis1_info[0] = NULL; + cis1_info[1] = NULL; + cis1_info[2] = NULL; + cis1_info[3] = NULL; + memset(cis1_info_buf, 0, 256); + + tmp = 0; + addr = cisptr; + + /* + * XXX Some parts of this code are taken + * from sys/dev/pccard/pccard_cis.c. + * Need to think about making it more abstract. + */ + do { + tuple_id = mmc_io_read_1(sc, 0, addr++); + if (tuple_id == SD_IO_CISTPL_END) + break; + tuple_len = mmc_io_read_1(sc, 0, addr++); + if (tuple_len == 0 && tuple_id != 0x00) { + device_printf(sc->dev, + "Parse error: 0-length tuple %02X\n", tuple_id); + break; + } + + switch (tuple_id) { + case SD_IO_CISTPL_VERS_1: + maninfo_p = addr; + + sdio_func->cis1_major = mmc_io_read_1(sc, 0, maninfo_p); + sdio_func->cis1_minor = mmc_io_read_1(sc, 0, maninfo_p + 1); + + for (count = 0, start = 0, i = 0; + (count < 4) && ((i + 4) < 256); i++) { + ch = mmc_io_read_1(sc, 0, maninfo_p + 2 + i); + if (ch == 0xff) + break; + cis1_info_buf[i] = ch; + if (ch == 0) { + cis1_info[count] = + cis1_info_buf + start; + start = i + 1; + count++; + } + } + + /* + * At least on Dreamplug there is only crap + * in these strings... + */ + device_printf(sc->dev, "*** Info[0]: %s\n", cis1_info[0]); + device_printf(sc->dev, "*** Info[1]: %s\n", cis1_info[1]); + device_printf(sc->dev, "*** Info[2]: %s\n", cis1_info[2]); + device_printf(sc->dev, "*** Info[3]: %s\n", cis1_info[3]); + break; + + case SD_IO_CISTPL_MANFID: + if (tuple_len < 4) { + device_printf(sc->dev, "MANFID is too short\n"); + break; + } + sdio_func->manufacturer = mmc_io_read_1(sc, 0, addr); + sdio_func->manufacturer |= mmc_io_read_1(sc, 0, addr + 1) << 8; + + sdio_func->product = mmc_io_read_1(sc, 0, addr + 2); + sdio_func->product |= mmc_io_read_1(sc, 0, addr + 3) << 8; + break; + + case SD_IO_CISTPL_FUNCID: + /* Function ID for SDIO devices is always 0x0C */ + if (tuple_len < 1) { + device_printf(sc->dev, "FUNCID is too short\n"); + break; + } + func_id = mmc_io_read_1(sc, 0, addr); + if (func_id != 0x0C) + device_printf(sc->dev, "func_id non-std: %d\n", func_id); + break; + + case SD_IO_CISTPL_FUNCE: + if (tuple_len < 4) { + device_printf(sc->dev, "FUNCE is too short\n"); + break; + } + uint8_t ext_data_type = mmc_io_read_1(sc, 0, addr); + + if (func == 0) { + if (ext_data_type != 0x0) + device_printf(sc->dev, + "funce for func 0 non-std: %d\n", + ext_data_type); + sdio_func->fn0_blksize = mmc_io_read_1(sc, 0, addr + 1); + sdio_func->fn0_blksize |= mmc_io_read_1(sc, 0, addr + 2) << 8; + + sdio_func->max_tran_speed = mmc_io_read_1(sc, 0, addr + 3); + /* XXX Do we need this? If yes, need to store in sdio_func */ + uint8_t max_tran_rate = sdio_func->max_tran_speed & 0x3; + uint8_t timecode = (sdio_func->max_tran_speed >> 3) & 0xF; + + device_printf(sc->dev, + "*** Max tran rate %d, timecode %d\n", + max_tran_rate, timecode); + } else { + /* ext_data_type is 1 here */ + /* + * XXX Do we need any information from FUNCE + * for non-0 functions? + */ + device_printf(sc->dev, + "Not parsing FUNCE for func != 0\n"); + } + + break; + + default: + device_printf(sc->dev, + "*** Skipping tuple ID %02X len %02X\n", + tuple_id, tuple_len); + break; + } + + addr += tuple_len; + tmp++; + } while (tuple_id != SD_IO_CISTPL_END && tmp < 10); + + return 0; +} + +/* + * Parse Card Common Control Register of the SDIO card + */ +static int +mmc_io_parse_cccr(struct mmc_softc *sc) +{ + uint32_t cisptr = 0; + + cisptr = mmc_io_read_1(sc, 0, SD_IO_CCCR_CISPTR); + cisptr |= mmc_io_read_1(sc, 0, SD_IO_CCCR_CISPTR + 1) << 8; + cisptr |= mmc_io_read_1(sc, 0, SD_IO_CCCR_CISPTR + 2) << 16; + + if (cisptr < SD_IO_CIS_START || + cisptr > SD_IO_CIS_START + SD_IO_CIS_SIZE) { + device_printf(sc->dev, "Bad CIS pointer in CCCR: %08X\n", cisptr); + return (-1); + } + + return mmc_io_parse_cis(sc, 0, cisptr, &sc->sdio_func0); +} + +/* + * Parse Function Basic Register of the given function + */ +static int +mmc_io_parse_fbr(struct mmc_softc *sc, uint8_t func) +{ + uint32_t fbr_addr, cisptr; + + fbr_addr = SD_IO_FBR_START * func + 0x9; + cisptr = mmc_io_read_1(sc, 0, fbr_addr); + cisptr |= mmc_io_read_1(sc, 0, fbr_addr + 1) << 8; + cisptr |= mmc_io_read_1(sc, 0, fbr_addr + 2) << 16; + + if (cisptr < SD_IO_CIS_START || + cisptr > SD_IO_CIS_START + SD_IO_CIS_SIZE) { + device_printf(sc->dev, "Bad CIS pointer in FBR: %08X\n", cisptr); + return (-1); + } + + struct sdio_function *f = malloc(sizeof(struct sdio_function), M_DEVBUF, M_WAITOK); + STAILQ_INSERT_TAIL(&sc->sdiof_head, f, sdiof_list); + + return mmc_io_parse_cis(sc, func, cisptr, f); +} + static void mmc_discover_cards(struct mmc_softc *sc) { @@ -1233,11 +1548,59 @@ mmc_discover_cards(struct mmc_softc *sc) device_t child; uint16_t rca = 2; u_char switch_res[64]; + uint8_t nfunc, mem_present; if (bootverbose || mmc_debug) device_printf(sc->dev, "Probing cards\n"); while (1) { - err = mmc_all_send_cid(sc, raw_cid); + /* + * Probe SDIO first, because SDIO cards don't have + * a CID register and won't respond to the CMD2 + */ + mmc_idle_cards(sc); + err = mmc_probe_sdio(sc, 0, NULL, &nfunc, &mem_present); + sc->sdio_nfunc = nfunc; + if (err != MMC_ERR_NONE && err != MMC_ERR_TIMEOUT) { + device_printf(sc->dev, "Error probing SDIO %d\n", err); + break; + } + + /* The card answered OK -> SDIO */ + if (err == MMC_ERR_NONE) { + device_printf(sc->dev, "Detected SDIO card\n"); + ivar = malloc(sizeof(struct mmc_ivars), M_DEVBUF, + M_WAITOK | M_ZERO); + mmc_send_relative_addr(sc, &resp); /* CMD3 */ + ivar->rca = resp >> 16; + err = mmc_select_card(sc, ivar->rca); /* CMD7 */ + if (err != MMC_ERR_NONE) { + device_printf(sc->dev, "Error selecting SDIO %d\n", err); + break; + } + + device_printf(sc->dev, "Get card info\n"); + mmc_io_parse_cccr(sc); + for(i=1; i <= nfunc; i++) { + device_printf(sc->dev, + "Get info for function %d\n", i); + mmc_io_parse_fbr(sc, i); + mmc_io_func_enable(sc, i); + } + + device_printf(sc->dev, "=== Functions ===\n"); + struct sdio_function *f; + + STAILQ_FOREACH(f, &sc->sdiof_head, sdiof_list) + device_printf(sc->dev, + "FN %d, vendor %04X, product %04X\n", + f->number, f->manufacturer, f->product + ); + + if (!mem_present) + return; + } + + err = mmc_all_send_cid(sc, raw_cid); /* Command 2 */ if (err == MMC_ERR_TIMEOUT) break; if (err != MMC_ERR_NONE) { @@ -1491,9 +1854,49 @@ mmc_delete_cards(struct mmc_softc *sc) return (0); } +/* CMD 5 */ +static int +mmc_probe_sdio(struct mmc_softc *sc, uint32_t ocr, uint32_t *rocr, uint8_t *nfunc, uint8_t *mem_present) { + struct mmc_command cmd; + int err = MMC_ERR_NONE, i; + + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = IO_SEND_OP_COND; + cmd.arg = 0; + cmd.flags = MMC_RSP_R4; + cmd.data = NULL; + + for (i = 0; i < 1000; i++) { + err = mmc_wait_for_cmd(sc, &cmd, CMD_RETRIES); + if (err != MMC_ERR_NONE) + break; + if ((cmd.resp[0] & MMC_OCR_CARD_BUSY) || + (ocr & MMC_OCR_VOLTAGE) == 0) + break; + err = MMC_ERR_TIMEOUT; + mmc_ms_delay(10); + } + + if (err == MMC_ERR_NONE) { + device_printf(sc->dev, "No timeout: OCR %08X, here are the values:\n", cmd.resp[0]); + if (rocr) + *rocr = cmd.resp[0]; + if (nfunc) + *nfunc = cmd.resp[0] >> 28 & 0x7; + if (mem_present) + *mem_present = cmd.resp[0] >> 27 & 0x1; + + device_printf(sc->dev, "NF: %d\n", cmd.resp[0] >> 28 & 0x7); + device_printf(sc->dev, "MEM: %d\n", cmd.resp[0] >> 27 & 0x1); + } + + return (err); +} + static void mmc_go_discovery(struct mmc_softc *sc) { + uint8_t nfunc, mem_present; uint32_t ocr; device_t dev; int err; @@ -1509,17 +1912,24 @@ mmc_go_discovery(struct mmc_softc *sc) if (bootverbose || mmc_debug) device_printf(sc->dev, "Probing bus\n"); mmc_idle_cards(sc); - err = mmc_send_if_cond(sc, 1); + err = mmc_send_if_cond(sc, 1); /* SD_SEND_IF_COND = 8 */ if ((bootverbose || mmc_debug) && err == 0) device_printf(sc->dev, "SD 2.0 interface conditions: OK\n"); - if (mmc_send_app_op_cond(sc, 0, &ocr) != MMC_ERR_NONE) { + if (mmc_probe_sdio(sc, 0, &ocr, &nfunc, &mem_present) == MMC_ERR_NONE) { + device_printf(dev, "SDIO probe OK (OCR: 0x%08x, %d functions, memory: %d)\n", ocr, nfunc, mem_present); + if (nfunc > 0 && mem_present) { + device_printf(sc->dev, "SDIO combo cards are not supported yet"); + return; + } + } else + if (mmc_send_app_op_cond(sc, 0, &ocr) != MMC_ERR_NONE) { /* retry 55 -> then 41 */ if (bootverbose || mmc_debug) device_printf(sc->dev, "SD probe: failed\n"); /* * Failed, try MMC */ mmcbr_set_mode(dev, mode_mmc); - if (mmc_send_op_cond(sc, 0, &ocr) != MMC_ERR_NONE) { + if (mmc_send_op_cond(sc, 0, &ocr) != MMC_ERR_NONE) { /* command 1 */ if (bootverbose || mmc_debug) device_printf(sc->dev, "MMC probe: failed\n"); ocr = 0; /* Failed both, powerdown. */ @@ -1553,9 +1963,11 @@ mmc_go_discovery(struct mmc_softc *sc) * Reselect the cards after we've idled them above. */ if (mmcbr_get_mode(dev) == mode_sd) { - err = mmc_send_if_cond(sc, 1); - mmc_send_app_op_cond(sc, - (err ? 0 : MMC_OCR_CCS) | mmcbr_get_ocr(dev), NULL); + if (mem_present) { + err = mmc_send_if_cond(sc, 1); /* CMD 8 */ + mmc_send_app_op_cond(sc, /* 41 -> 55 */ + (err ? 0 : MMC_OCR_CCS) | mmcbr_get_ocr(dev), NULL); + } } else mmc_send_op_cond(sc, mmcbr_get_ocr(dev), NULL); mmc_discover_cards(sc); @@ -1637,7 +2049,7 @@ mmc_read_ivar(device_t bus, device_t child, int which, uintptr_t *result) return (EINVAL); case MMC_IVAR_DSR_IMP: *result = ivar->csd.dsr_imp; - break; + break; case MMC_IVAR_MEDIA_SIZE: *result = ivar->sec_count; break; @@ -1735,3 +2147,4 @@ DRIVER_MODULE(mmc, at91_mci, mmc_driver, mmc_devclass, NULL, NULL); DRIVER_MODULE(mmc, sdhci_pci, mmc_driver, mmc_devclass, NULL, NULL); DRIVER_MODULE(mmc, sdhci_bcm, mmc_driver, mmc_devclass, NULL, NULL); DRIVER_MODULE(mmc, sdhci_fdt, mmc_driver, mmc_devclass, NULL, NULL); +DRIVER_MODULE(mmc, sdio, mmc_driver, mmc_devclass, NULL, NULL); diff --git a/sys/dev/mmc/mmcioreg.h b/sys/dev/mmc/mmcioreg.h new file mode 100644 index 0000000..840a863 --- /dev/null +++ b/sys/dev/mmc/mmcioreg.h @@ -0,0 +1,95 @@ +/* $OpenBSD: sdmmc_ioreg.h,v 1.4 2007/06/02 01:48:37 uwe Exp $ */ +/* $FreeBSD$ */ + +/* + * Copyright (c) 2006 Uwe Stuehler + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _SDMMC_IOREG_H +#define _SDMMC_IOREG_H + +/* SDIO commands */ /* response type */ +#define SD_IO_SEND_OP_COND 5 /* R4 */ +#define SD_IO_RW_DIRECT 52 /* R5 */ +#define SD_IO_RW_EXTENDED 53 /* R5? */ + +/* CMD52 arguments */ +#define SD_ARG_CMD52_READ (0<<31) +#define SD_ARG_CMD52_WRITE (1<<31) +#define SD_ARG_CMD52_FUNC_SHIFT 28 +#define SD_ARG_CMD52_FUNC_MASK 0x7 +#define SD_ARG_CMD52_EXCHANGE (1<<27) +#define SD_ARG_CMD52_REG_SHIFT 9 +#define SD_ARG_CMD52_REG_MASK 0x1ffff +#define SD_ARG_CMD52_DATA_SHIFT 0 +#define SD_ARG_CMD52_DATA_MASK 0xff +#define SD_R5_DATA(resp) ((resp)[0] & 0xff) + +/* CMD53 arguments */ +#define SD_ARG_CMD53_READ (0<<31) +#define SD_ARG_CMD53_WRITE (1<<31) +#define SD_ARG_CMD53_FUNC_SHIFT 28 +#define SD_ARG_CMD53_FUNC_MASK 0x7 +#define SD_ARG_CMD53_BLOCK_MODE (1<<27) +#define SD_ARG_CMD53_INCREMENT (1<<26) +#define SD_ARG_CMD53_REG_SHIFT 9 +#define SD_ARG_CMD53_REG_MASK 0x1ffff +#define SD_ARG_CMD53_LENGTH_SHIFT 0 +#define SD_ARG_CMD53_LENGTH_MASK 0x1ff +#define SD_ARG_CMD53_LENGTH_MAX 64 /* XXX should be 511? */ + +/* 48-bit response decoding (32 bits w/o CRC) */ +#define MMC_R4(resp) ((resp)[0]) +#define MMC_R5(resp) ((resp)[0]) + +/* SD R4 response (IO OCR) */ +#define SD_IO_OCR_MEM_READY (1<<31) +#define SD_IO_OCR_NUM_FUNCTIONS(ocr) (((ocr) >> 28) & 0x3) +/* XXX big fat memory present "flag" because we don't know better */ +#define SD_IO_OCR_MEM_PRESENT (0xf<<24) +#define SD_IO_OCR_MASK 0x00fffff0 + +/* Card Common Control Registers (CCCR) */ +#define SD_IO_CCCR_START 0x00000 +#define SD_IO_CCCR_SIZE 0x100 +#define SD_IO_CCCR_FN_ENABLE 0x02 +#define SD_IO_CCCR_FN_READY 0x03 +#define SD_IO_CCCR_INT_ENABLE 0x04 +#define SD_IO_CCCR_CTL 0x06 +#define CCCR_CTL_RES (1<<3) +#define SD_IO_CCCR_BUS_WIDTH 0x07 +#define CCCR_BUS_WIDTH_4 (1<<1) +#define CCCR_BUS_WIDTH_1 (1<<0) +#define SD_IO_CCCR_CISPTR 0x09 /* XXX 9-10, 10-11, or 9-12 */ + +/* Function Basic Registers (FBR) */ +#define SD_IO_FBR_START 0x00100 +#define SD_IO_FBR_SIZE 0x00700 + +/* Card Information Structure (CIS) */ +#define SD_IO_CIS_START 0x01000 +#define SD_IO_CIS_SIZE 0x17000 + +/* CIS tuple codes (based on PC Card 16) */ +#define SD_IO_CISTPL_VERS_1 0x15 +#define SD_IO_CISTPL_MANFID 0x20 +#define SD_IO_CISTPL_FUNCID 0x21 +#define SD_IO_CISTPL_FUNCE 0x22 +#define SD_IO_CISTPL_END 0xff + +/* CISTPL_FUNCID codes */ +/* OpenBSD incorrectly defines 0x0c as FUNCTION_WLAN */ +/* #define SDMMC_FUNCTION_WLAN 0x0c */ +#endif diff --git a/sys/dev/mmc/mmcreg.h b/sys/dev/mmc/mmcreg.h index f454ddb..4b65d91 100644 --- a/sys/dev/mmc/mmcreg.h +++ b/sys/dev/mmc/mmcreg.h @@ -85,6 +85,8 @@ struct mmc_command { #define MMC_RSP_R1B (MMC_RSP_PRESENT | MMC_RSP_CRC | MMC_RSP_OPCODE | MMC_RSP_BUSY) #define MMC_RSP_R2 (MMC_RSP_PRESENT | MMC_RSP_136 | MMC_RSP_CRC) #define MMC_RSP_R3 (MMC_RSP_PRESENT) +#define MMC_RSP_R4 (MMC_RSP_PRESENT) +#define MMC_RSP_R5 (MMC_RSP_PRESENT | MMC_RSP_CRC | MMC_RSP_OPCODE) #define MMC_RSP_R6 (MMC_RSP_PRESENT | MMC_RSP_CRC) #define MMC_RSP_R7 (MMC_RSP_PRESENT | MMC_RSP_CRC) #define MMC_RSP(x) ((x) & MMC_RSP_MASK) @@ -151,6 +153,30 @@ struct mmc_command { #define R1_STATE_PRG 7 #define R1_STATE_DIS 8 +/* + * R5 responses + * + * Types (per SD 2.0 standard) + *e : error bit + *s : status bit + *r : detected and set for the actual command response + *x : Detected and set during command execution. The host can get + * the status by issuing a command with R1 response. + * + * Clear Condition (per SD 2.0 standard) + *a : according to the card current state. + *b : always related to the previous command. reception of a valid + * command will clear it (with a delay of one command). + *c : clear by read + */ +#define R5_COM_CRC_ERROR (1u << 15)/* er, b */ +#define R5_ILLEGAL_COMMAND (1u << 14)/* er, b */ +#define R5_IO_CURRENT_STATE_MASK (3u << 12)/* s, b */ +#define R5_IO_CURRENT_STATE(x) (((x) & R5_IO_CURRENT_STATE_MASK) >> 12) +#define R5_ERROR (1u << 11)/* erx, c */ +#define R5_FUNCTION_NUMBER (1u << 9)/* er, c */ +#define R5_OUT_OF_RANGE (1u << 8)/* er, c */ + struct mmc_data { size_t len; /* size of the data */ size_t xfer_len; @@ -181,7 +207,7 @@ struct mmc_request { #define MMC_SET_RELATIVE_ADDR 3 #define SD_SEND_RELATIVE_ADDR 3 #define MMC_SET_DSR 4 - /* reserved: 5 */ +#define IO_SEND_OP_COND 5 #define MMC_SWITCH_FUNC 6 #define MMC_SWITCH_FUNC_CMDS 0 #define MMC_SWITCH_FUNC_SET 1 @@ -335,6 +361,20 @@ struct mmc_request { #define SD_MAX_HS 50000000 +/* + * SDIO Direct & Extended I/O + */ +#define SD_IO_RW_WR (1u << 31) +#define SD_IO_RW_FUNC(x) (((x) & 0x7) << 28) +#define SD_IO_RW_RAW (1u << 27) +#define SD_IO_RW_INCR (1u << 26) +#define SD_IO_RW_ADR(x) (((x) & 0x1FFFF) << 9) +#define SD_IO_RW_DAT(x) (((x) & 0xFF) << 0) +#define SD_IO_RW_LEN(x) (((x) & 0xFF) << 0) + +#define SD_IOE_RW_LEN(x) (((x) & 0x1FF) << 0) +#define SD_IOE_RW_BLK (1u << 27) + /* OCR bits */ /*