Date: Tue, 2 Jul 2013 16:59:05 +0200 From: Ilya Bakulin <ilya@bakulin.de> To: freebsd-arm@freebsd.org, freebsd-embedded@freebsd.org Cc: mav@FreeBSD.org Subject: [PATCH] SDIO support for Globalscale Dreamplug Message-ID: <20130702145905.GA1847@olymp.kibab.com>
next in thread | raw e-mail | index | archive | help
Hi list, I'm currently developing a SDIO driver for the Globalscale Dreamplug. I have taken SDIO patch for Marvell SoC from [1]. After that I have written some SDIO-related code in sys/dev/mmc/mmc.c, using OpenBSD SDIO code and the patch from Ben Gray ([2]) as a starting point. I have taken Warner's wish to have SDIO code in MMC bus into account, so there is no extra layer of abstraction in my code, SDIO devices will attach directly to MMC bus. This makes possible to implement combo cards support in the future, although I don't support them in my code atm. What is already implemented: * SDIO card detection; * CIS reading, both common CIS and individual functions' CIS; * Function enable. My questions, need answers before I can move further: * Where should I store information retrieved from the CIS? As far as I understand, I should use mmc_ivars structure for that. But in SDIO case the relationship between MMC bus and SDIO card is 1:1, and storing the information about the card in mmc_softc sounds like a good idea -- then I can pass only mmc_softc structure to all functions that need to work with the attached SDIO card. * Should I add any methods to the existing interface files? * Are there any devices on the market that use SDIO interface and which chipsets are supported in FreeBSD? Any Atheros devices? Adrian, what do you think? I have only Dreamplug with Marvell SDIO-based WLAN chip, that doesn't have an opensource driver even for Linux... [1] http://people.freebsd.org/~raj/misc/mv_sdio.c [2] http://lists.freebsd.org/pipermail/freebsd-arm/2012-June/003543.html 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 <sys/cdefs.h> + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/module.h> +#include <sys/mutex.h> +#include <sys/rman.h> +#include <sys/sysctl.h> +#include <sys/systm.h> +#include <sys/taskqueue.h> + +#include <arm/mv/mvreg.h> +#include <arm/mv/mvvar.h> + +#include <machine/bus.h> +#include <machine/intr.h> + +#include <dev/mmc/bridge.h> +#include <dev/mmc/mmcreg.h> +#include <dev/mmc/mmcvar.h> +#include <dev/mmc/mmcbrvar.h> + +#include <dev/ofw/ofw_bus.h> +#include <dev/ofw/ofw_bus_subr.h> + +#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.<unit>.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..6fcfb32 100644 --- a/sys/dev/mmc/mmc.c +++ b/sys/dev/mmc/mmc.c @@ -67,6 +67,8 @@ __FBSDID("$FreeBSD$"); #include <dev/mmc/mmcreg.h> #include <dev/mmc/mmcbrvar.h> #include <dev/mmc/mmcvar.h> +#include <dev/mmc/mmcioreg.h> + #include "mmcbr_if.h" #include "mmcbus_if.h" @@ -96,11 +98,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 +163,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, @@ -470,6 +479,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 +504,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 +532,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 +560,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 +613,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 +1056,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 +1177,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 +1193,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 +1240,291 @@ 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) +{ + /* + * XXX Need a structure to store all information that we get from CIS! + * But where to place it after that?.. + */ + uint32_t tmp; + + uint8_t tuple_id, tuple_len, func_id; + uint32_t addr, maninfo_p; + uint16_t manufacturer, product; + uint16_t fn0_blksize; + uint8_t max_tran_speed; + uint8_t cis1_major, cis1_minor; + char *cis1_info[4]; + + int start, i, ch, count; + char cis1_info_buf[256]; + + 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; + + cis1_major = mmc_io_read_1(sc, 0, maninfo_p); + 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++; + } + } + + 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; + } + manufacturer = mmc_io_read_1(sc, 0, addr); + manufacturer |= mmc_io_read_1(sc, 0, addr + 1) << 8; + + product = mmc_io_read_1(sc, 0, addr + 2); + product |= mmc_io_read_1(sc, 0, addr + 3) << 8; + + device_printf(sc->dev, + "*** Vendor %04X, product %04X\n", + manufacturer, product); + + 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); + device_printf(sc->dev, "*** Function ext type %d\n", + ext_data_type); + + if (func == 0) { + if (ext_data_type != 0x0) + device_printf(sc->dev, + "funce for func 0 non-std: %d\n", + ext_data_type); + fn0_blksize = mmc_io_read_1(sc, 0, addr + 1); + fn0_blksize |= mmc_io_read_1(sc, 0, addr + 2) << 8; + + max_tran_speed = mmc_io_read_1(sc, 0, addr + 3); + uint8_t max_tran_rate = max_tran_speed & 0x3; + uint8_t timecode = (max_tran_speed >> 3) & 0xF; + + device_printf(sc->dev, + "*** Max tran rate %d, timecode %d\n", + max_tran_rate, timecode); + } else { + /* + * XXX Do we need any information from FUNCE + * for non-0 functions? + */ + device_printf(sc->dev, + "I don't know how to parse FUNCE\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); +} + +/* + * 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); + } + + return mmc_io_parse_cis(sc, func, cisptr); +} + static void mmc_discover_cards(struct mmc_softc *sc) { @@ -1233,11 +1535,48 @@ 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); + 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); + } + 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 +1830,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 +1888,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 +1939,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 +2025,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 +2123,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 <uwe@openbsd.org> + * + * 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 */ /*
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20130702145905.GA1847>