From: Sascha Wildner Date: Fri, 16 Apr 2010 07:57:29 +0000 (+0200) Subject: Bring in FreeBSD's MMC (MultiMediaCard) stack. X-Git-Tag: v2.7.1~6 X-Git-Url: https://gitweb.dragonflybsd.org/dragonfly.git/commitdiff_plain/d664467926fd89b28ccdda75ae1bb5863fa3671b Bring in FreeBSD's MMC (MultiMediaCard) stack. mmc(4) - The MMC bus sdhci(4) - Host controller driver mmcsd(4) - Memory card disk device driver SD, SDHC and MMC cards should be supported (tested with an SDHC card). --- diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile index 241d4f381e..e5ebb521d3 100644 --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -1,6 +1,5 @@ # @(#)Makefile 8.1 (Berkeley) 6/18/93 # $FreeBSD: src/share/man/man4/Makefile,v 1.83.2.66 2003/06/04 17:10:30 sam Exp $ -# $DragonFly: src/share/man/man4/Makefile,v 1.88 2008/10/03 00:26:21 hasso Exp $ MAN= aac.4 \ acpi.4 \ @@ -143,6 +142,8 @@ MAN= aac.4 \ miibus.4 \ mlx.4 \ mly.4 \ + mmc.4 \ + mmcsd.4 \ mn.4 \ moscom.4 \ mouse.4 \ @@ -230,6 +231,7 @@ MAN= aac.4 \ screen.4 \ scsi.4 \ sd.4 \ + sdhci.4 \ ses.4 \ sf.4 \ si.4 \ diff --git a/share/man/man4/mmc.4 b/share/man/man4/mmc.4 new file mode 100644 index 0000000000..c0ae38437d --- /dev/null +++ b/share/man/man4/mmc.4 @@ -0,0 +1,66 @@ +.\" +.\" Copyright (c) 2007 M. Warner Losh +.\" 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. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission. +.\" +.\" 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. +.\" +.\" $FreeBSD: src/share/man/man4/mmc.4,v 1.3 2008/10/22 09:04:05 mav Exp $ +.\" +.Dd April 16, 2010 +.Dt MMC 4 +.Os +.Sh NAME +.Nm mmc +.Nd MultiMediaCard and SD Card bus driver +.Sh SYNOPSIS +.Cd device mmc +.Sh DESCRIPTION +The +.Nm +driver implements the MMC and SD Card bus. +The +.Nm +driver supports all MMC and SD bridges in the system. +All SD or MMC cards in the system attach to an instance of +.Nm . +The +.Nm +bus typically has only one slot, and only memory cards. +MultiMediaCards exist only in memory. +SD Cards exist as memory, I/O, or combination cards. +.Sh SEE ALSO +.Xr mmcsd 4 , +.Xr sdhci 4 +.Rs +.%T "SD Specifications, Part 1, Physical Layer, Simplified Specification" +.Re +.Rs +.%T "The MultiMediaCard System Specification" +.Re +.Sh HISTORY +The +.Nm +driver was imported from +.Fx +into +.Dx 2.7 . +.Sh BUGS +SDIO cards currently do not work. diff --git a/share/man/man4/mmcsd.4 b/share/man/man4/mmcsd.4 new file mode 100644 index 0000000000..603326c703 --- /dev/null +++ b/share/man/man4/mmcsd.4 @@ -0,0 +1,54 @@ +.\" +.\" Copyright (c) 2007 M. Warner Losh +.\" 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. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission. +.\" +.\" 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. +.\" +.\" $FreeBSD: src/share/man/man4/mmcsd.4,v 1.4 2008/10/22 09:11:35 mav Exp $ +.\" +.Dd April 16, 2010 +.Dt MMCSD 4 +.Os +.Sh NAME +.Nm mmcsd +.Nd MMC and SD memory card driver. +.Sh SYNOPSIS +.Cd device mmcsd +.Sh DESCRIPTION +The +.Nm +driver implements direct access block device for MMC and SD memory cards. +.Sh SEE ALSO +.Xr mmc 4 , +.Xr sdhci 4 +.Rs +.%T "SD Specifications, Part 1, Physical Layer, Simplified Specification" +.Re +.Rs +.%T "The MultiMediaCard System Specification" +.Re +.Sh HISTORY +The +.Nm +driver was imported from +.Fx +into +.Dx 2.7 . diff --git a/share/man/man4/sdhci.4 b/share/man/man4/sdhci.4 new file mode 100644 index 0000000000..64d653de8a --- /dev/null +++ b/share/man/man4/sdhci.4 @@ -0,0 +1,93 @@ +.\" +.\" Copyright (c) 2008 Alexander Motin +.\" 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. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission. +.\" +.\" 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. +.\" +.\" $FreeBSD: src/share/man/man4/sdhci.4,v 1.2 2009/01/16 19:28:25 mav Exp $ +.\" +.Dd April 16, 2010 +.Dt SDHCI 4 +.Os +.Sh NAME +.Nm sdhci +.Nd PCI SD Host Controller bridge driver +.Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device mmc" +.Cd "device mmcsd" +.Cd "device sdhci" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following lines in +.Xr loader.conf 5 : +.Bd -literal -offset indent +mmc_load="YES" +mmcsd_load="YES" +sdhci_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver supports PCI devices with class 8 and subclass 5 according to +SD Host Controller Specification. +Driver supports up to six high speed 4bit MMC/SD slots per controller. +Driver attaches mmc bus to the respective slot on card insertion and +detaches it on card removing. +.Sh HARDWARE +The +.Nm +driver supports different specification compatible chips. The following +chips have been verified to work: +.Pp +.Bl -bullet -compact +.It +ENE CB712 +.It +ENE CB714 +.It +RICOH R5C822 +.It +TI PCIXX21/XX11 +.El +.Sh SEE ALSO +.Xr mmc 4 , +.Xr mmcsd 4 +.Rs +.%T "SD Specifications, Part 2, SD Host Controller, Simplified Specification" +.Re +.Sh HISTORY +The +.Nm +driver was imported from +.Fx +into +.Dx 2.7 . +.Sh AUTHORS +.An Alexander Motin Aq mav@FreeBSD.org . +.Sh BUGS +Many of existing SD controller chips have some nonstandard requirements, +proprietary registers and hardware bugs, requiring additional handling. +ENE chips are handled to work fine, while some revisions of RICOH and TI +controllers still don't see cards without some additional initialization. diff --git a/sys/bus/Makefile b/sys/bus/Makefile index 233d032fe1..124f34c4e7 100644 --- a/sys/bus/Makefile +++ b/sys/bus/Makefile @@ -1,6 +1,3 @@ -# $DragonFly: src/sys/bus/Makefile,v 1.4 2005/04/22 22:02:58 swildner Exp $ -# - -SUBDIR=cam firewire usb pccard +SUBDIR=cam firewire mmc pccard usb .include diff --git a/sys/bus/mmc/Makefile b/sys/bus/mmc/Makefile new file mode 100644 index 0000000000..06d2c1517b --- /dev/null +++ b/sys/bus/mmc/Makefile @@ -0,0 +1,8 @@ +# $FreeBSD: src/sys/modules/mmc/Makefile,v 1.1 2006/10/20 06:52:59 imp Exp $ + +.PATH: ${.CURDIR}/../../dev/mmc + +KMOD= mmc +SRCS= mmc.c mmcbr_if.h mmcbus_if.h device_if.h bus_if.h + +.include diff --git a/sys/bus/mmc/bridge.h b/sys/bus/mmc/bridge.h new file mode 100644 index 0000000000..d08421c541 --- /dev/null +++ b/sys/bus/mmc/bridge.h @@ -0,0 +1,138 @@ +/*- + * Copyright (c) 2006 M. Warner Losh. 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 ``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 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. + * + * Portions of this software may have been developed with reference to + * the SD Simplified Specification. The following disclaimer may apply: + * + * The following conditions apply to the release of the simplified + * specification ("Simplified Specification") by the SD Card Association and + * the SD Group. The Simplified Specification is a subset of the complete SD + * Specification which is owned by the SD Card Association and the SD + * Group. This Simplified Specification is provided on a non-confidential + * basis subject to the disclaimers below. Any implementation of the + * Simplified Specification may require a license from the SD Card + * Association, SD Group, SD-3C LLC or other third parties. + * + * Disclaimers: + * + * The information contained in the Simplified Specification is presented only + * as a standard specification for SD Cards and SD Host/Ancillary products and + * is provided "AS-IS" without any representations or warranties of any + * kind. No responsibility is assumed by the SD Group, SD-3C LLC or the SD + * Card Association for any damages, any infringements of patents or other + * right of the SD Group, SD-3C LLC, the SD Card Association or any third + * parties, which may result from its use. No license is granted by + * implication, estoppel or otherwise under any patent or other rights of the + * SD Group, SD-3C LLC, the SD Card Association or any third party. Nothing + * herein shall be construed as an obligation by the SD Group, the SD-3C LLC + * or the SD Card Association to disclose or distribute any technical + * information, know-how or other confidential information to any third party. + * + * $FreeBSD: src/sys/dev/mmc/bridge.h,v 1.3 2008/10/08 17:35:41 mav Exp $ + */ + +#ifndef BUS_MMC_BRIDGE_H +#define BUS_MMC_BRIDGE_H + +/* + * This file defines interfaces for the mmc bridge. The names chosen + * are similar to or the same as the names used in Linux to allow for + * easy porting of what Linux calls mmc host drivers. I use the + * FreeBSD terminology of bridge and bus for consistancy with other + * drivers in the system. This file corresponds roughly to the Linux + * linux/mmc/host.h file. + * + * A mmc bridge is a chipset that can have one or more mmc and/or sd + * cards attached to it. mmc cards are attached on a bus topology, + * while sd and sdio cards are attached using a star topology (meaning + * in practice each sd card has its own, independent slot). Each + * mmcbr is assumed to be derived from the mmcbr. This is done to + * allow for easier addition of bridges (as each bridge does not need + * to be added to the mmcbus file). + * + * Attached to the mmc bridge is an mmcbus. The mmcbus is described + * in dev/mmc/bus.h. + */ + + +/* + * mmc_ios is a structure that is used to store the state of the mmc/sd + * bus configuration. This include the bus' clock speed, its voltage, + * the bus mode for command output, the SPI chip select, some power + * states and the bus width. + */ +enum mmc_vdd { + vdd_150 = 0, vdd_155, vdd_160, vdd_165, vdd_170, vdd_180, + vdd_190, vdd_200, vdd_210, vdd_220, vdd_230, vdd_240, vdd_250, + vdd_260, vdd_270, vdd_280, vdd_290, vdd_300, vdd_310, vdd_320, + vdd_330, vdd_340, vdd_350, vdd_360 +}; + +enum mmc_power_mode { + power_off = 0, power_up, power_on +}; + +enum mmc_bus_mode { + opendrain = 1, pushpull +}; + +enum mmc_chip_select { + cs_dontcare = 0, cs_high, cs_low +}; + +enum mmc_bus_width { + bus_width_1 = 0, bus_width_4 = 2, bus_width_8 = 3 +}; + +enum mmc_bus_timing { + bus_timing_normal = 0, bus_timing_hs +}; + +struct mmc_ios { + uint32_t clock; /* Speed of the clock in Hz to move data */ + enum mmc_vdd vdd; /* Voltage to apply to the power pins/ */ + enum mmc_bus_mode bus_mode; + enum mmc_chip_select chip_select; + enum mmc_bus_width bus_width; + enum mmc_power_mode power_mode; + enum mmc_bus_timing timing; +}; + +enum mmc_card_mode { + mode_mmc, mode_sd +}; + +struct mmc_host { + int f_min; + int f_max; + uint32_t host_ocr; + uint32_t ocr; + uint32_t caps; +#define MMC_CAP_4_BIT_DATA (1 << 0) /* Can do 4-bit data transfers */ +#define MMC_CAP_8_BIT_DATA (1 << 1) /* Can do 8-bit data transfers */ +#define MMC_CAP_HSPEED (1 << 2) /* Can do High Speed transfers */ + enum mmc_card_mode mode; + struct mmc_ios ios; /* Current state of the host */ +}; + +#endif /* BUS_MMC_BRIDGE_H */ diff --git a/sys/bus/mmc/mmc.c b/sys/bus/mmc/mmc.c new file mode 100644 index 0000000000..7a19fb8773 --- /dev/null +++ b/sys/bus/mmc/mmc.c @@ -0,0 +1,1529 @@ +/*- + * Copyright (c) 2006 Bernd Walter. All rights reserved. + * Copyright (c) 2006 M. Warner Losh. 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 ``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 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. + * + * Portions of this software may have been developed with reference to + * the SD Simplified Specification. The following disclaimer may apply: + * + * The following conditions apply to the release of the simplified + * specification ("Simplified Specification") by the SD Card Association and + * the SD Group. The Simplified Specification is a subset of the complete SD + * Specification which is owned by the SD Card Association and the SD + * Group. This Simplified Specification is provided on a non-confidential + * basis subject to the disclaimers below. Any implementation of the + * Simplified Specification may require a license from the SD Card + * Association, SD Group, SD-3C LLC or other third parties. + * + * Disclaimers: + * + * The information contained in the Simplified Specification is presented only + * as a standard specification for SD Cards and SD Host/Ancillary products and + * is provided "AS-IS" without any representations or warranties of any + * kind. No responsibility is assumed by the SD Group, SD-3C LLC or the SD + * Card Association for any damages, any infringements of patents or other + * right of the SD Group, SD-3C LLC, the SD Card Association or any third + * parties, which may result from its use. No license is granted by + * implication, estoppel or otherwise under any patent or other rights of the + * SD Group, SD-3C LLC, the SD Card Association or any third party. Nothing + * herein shall be construed as an obligation by the SD Group, the SD-3C LLC + * or the SD Card Association to disclose or distribute any technical + * information, know-how or other confidential information to any third party. + * + * $FreeBSD: src/sys/dev/mmc/mmc.c,v 1.38 2009/08/20 19:17:53 jhb Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "mmcbr_if.h" +#include "mmcbus_if.h" + +struct mmc_softc { + device_t dev; + struct lock sc_lock; + struct intr_config_hook config_intrhook; + device_t owner; + uint32_t last_rca; +}; + +/* + * Per-card data + */ +struct mmc_ivars { + uint32_t raw_cid[4]; /* Raw bits of the CID */ + uint32_t raw_csd[4]; /* Raw bits of the CSD */ + uint32_t raw_scr[2]; /* Raw bits of the SCR */ + uint8_t raw_ext_csd[512]; /* Raw bits of the EXT_CSD */ + uint32_t raw_sd_status[16]; /* Raw bits of the SD_STATUS */ + uint16_t rca; + enum mmc_card_mode mode; + struct mmc_cid cid; /* cid decoded */ + struct mmc_csd csd; /* csd decoded */ + struct mmc_scr scr; /* scr decoded */ + struct mmc_sd_status sd_status; /* SD_STATUS decoded */ + 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 */ + 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 */ +}; + +#define CMD_RETRIES 3 + +SYSCTL_NODE(_hw, OID_AUTO, mmc, CTLFLAG_RD, NULL, "mmc driver"); + +static int mmc_debug; +SYSCTL_INT(_hw_mmc, OID_AUTO, debug, CTLFLAG_RW, &mmc_debug, 0, "Debug level"); + +/* bus entry points */ +static int mmc_probe(device_t dev); +static int mmc_attach(device_t dev); +static int mmc_detach(device_t dev); +static int mmc_suspend(device_t dev); +static int mmc_resume(device_t dev); + +#define MMC_LOCK(_sc) lockmgr(&(_sc)->sc_lock, LK_EXCLUSIVE) +#define MMC_UNLOCK(_sc) lockmgr(&(_sc)->sc_lock, LK_RELEASE) +#define MMC_LOCK_INIT(_sc) lockinit(&(_sc)->sc_lock, "mmc", 0, LK_CANRECURSE) +#define MMC_LOCK_DESTROY(_sc) lockuninit(&(_sc)->sc_lock); +#define MMC_ASSERT_LOCKED(_sc) KKASSERT(lockstatus(&(_sc)->sc_lock, curthread) != 0); +#define MMC_ASSERT_UNLOCKED(_sc) KKASSERT(lockstatus(&(_sc)->sc_lock, curthread) == 0); + +static int mmc_calculate_clock(struct mmc_softc *sc); +static void mmc_delayed_attach(void *); +static void mmc_power_down(struct mmc_softc *sc); +static int mmc_wait_for_cmd(struct mmc_softc *sc, struct mmc_command *cmd, + int retries); +static int mmc_wait_for_command(struct mmc_softc *sc, uint32_t opcode, + uint32_t arg, uint32_t flags, uint32_t *resp, int retries); +static int mmc_select_card(struct mmc_softc *sc, uint16_t rca); +static int mmc_set_card_bus_width(struct mmc_softc *sc, uint16_t rca, int width); +static int mmc_app_send_scr(struct mmc_softc *sc, uint16_t rca, uint32_t *rawscr); +static void mmc_app_decode_scr(uint32_t *raw_scr, struct mmc_scr *scr); +static int mmc_send_ext_csd(struct mmc_softc *sc, uint8_t *rawextcsd); +static void mmc_scan(struct mmc_softc *sc); +static int mmc_delete_cards(struct mmc_softc *sc); + +static void +mmc_ms_delay(int ms) +{ + DELAY(1000 * ms); /* XXX BAD */ +} + +static int +mmc_probe(device_t dev) +{ + + device_set_desc(dev, "MMC/SD bus"); + return (0); +} + +static int +mmc_attach(device_t dev) +{ + struct mmc_softc *sc; + + sc = device_get_softc(dev); + sc->dev = dev; + MMC_LOCK_INIT(sc); + + /* 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; + if (config_intrhook_establish(&sc->config_intrhook) != 0) + device_printf(dev, "config_intrhook_establish failed\n"); + return (0); +} + +static int +mmc_detach(device_t dev) +{ + struct mmc_softc *sc = device_get_softc(dev); + int err; + + if ((err = mmc_delete_cards(sc)) != 0) + return (err); + mmc_power_down(sc); + MMC_LOCK_DESTROY(sc); + + return (0); +} + +static int +mmc_suspend(device_t dev) +{ + struct mmc_softc *sc = device_get_softc(dev); + int err; + + err = bus_generic_suspend(dev); + if (err) + return (err); + mmc_power_down(sc); + return (0); +} + +static int +mmc_resume(device_t dev) +{ + struct mmc_softc *sc = device_get_softc(dev); + + mmc_scan(sc); + return (bus_generic_resume(dev)); +} + +static int +mmc_acquire_bus(device_t busdev, device_t dev) +{ + struct mmc_softc *sc; + struct mmc_ivars *ivar; + int err; + int rca; + + err = MMCBR_ACQUIRE_HOST(device_get_parent(busdev), busdev); + if (err) + return (err); + sc = device_get_softc(busdev); + MMC_LOCK(sc); + if (sc->owner) + panic("mmc: host bridge didn't seralize us."); + sc->owner = dev; + MMC_UNLOCK(sc); + + if (busdev != dev) { + /* + * Keep track of the last rca that we've selected. If + * we're asked to do it again, don't. We never + * unselect unless the bus code itself wants the mmc + * bus, and constantly reselecting causes problems. + */ + rca = mmc_get_rca(dev); + if (sc->last_rca != rca) { + mmc_select_card(sc, rca); + sc->last_rca = rca; + /* Prepare bus width for the new card. */ + ivar = device_get_ivars(dev); + if (bootverbose || mmc_debug) { + device_printf(busdev, + "setting bus width to %d bits\n", + (ivar->bus_width == bus_width_4) ? 4 : + (ivar->bus_width == bus_width_8) ? 8 : 1); + } + mmc_set_card_bus_width(sc, rca, ivar->bus_width); + mmcbr_set_bus_width(busdev, ivar->bus_width); + mmcbr_update_ios(busdev); + } + } else { + /* + * If there's a card selected, stand down. + */ + if (sc->last_rca != 0) { + mmc_select_card(sc, 0); + sc->last_rca = 0; + } + } + + return (0); +} + +static int +mmc_release_bus(device_t busdev, device_t dev) +{ + struct mmc_softc *sc; + int err; + + sc = device_get_softc(busdev); + + MMC_LOCK(sc); + if (!sc->owner) + panic("mmc: releasing unowned bus."); + if (sc->owner != dev) + panic("mmc: you don't own the bus. game over."); + MMC_UNLOCK(sc); + err = MMCBR_RELEASE_HOST(device_get_parent(busdev), busdev); + if (err) + return (err); + MMC_LOCK(sc); + sc->owner = NULL; + MMC_UNLOCK(sc); + return (0); +} + +static uint32_t +mmc_select_vdd(struct mmc_softc *sc, uint32_t ocr) +{ + + return (ocr & MMC_OCR_VOLTAGE); +} + +static int +mmc_highest_voltage(uint32_t ocr) +{ + int i; + + for (i = 30; i >= 0; i--) + if (ocr & (1 << i)) + return (i); + return (-1); +} + +static void +mmc_wakeup(struct mmc_request *req) +{ + struct mmc_softc *sc; + + sc = (struct mmc_softc *)req->done_data; + MMC_LOCK(sc); + req->flags |= MMC_REQ_DONE; + MMC_UNLOCK(sc); + wakeup(req); +} + +static int +mmc_wait_for_req(struct mmc_softc *sc, struct mmc_request *req) +{ + + req->done = mmc_wakeup; + req->done_data = sc; + if (mmc_debug > 1) { + device_printf(sc->dev, "REQUEST: CMD%d arg %#x flags %#x", + req->cmd->opcode, req->cmd->arg, req->cmd->flags); + if (req->cmd->data) { + kprintf(" data %d\n", (int)req->cmd->data->len); + } else + kprintf("\n"); + } + MMCBR_REQUEST(device_get_parent(sc->dev), sc->dev, req); + MMC_LOCK(sc); + while ((req->flags & MMC_REQ_DONE) == 0) + lksleep(req, &sc->sc_lock, 0, "mmcreq", 0); + MMC_UNLOCK(sc); + if (mmc_debug > 2 || (mmc_debug > 1 && req->cmd->error)) + device_printf(sc->dev, "RESULT: %d\n", req->cmd->error); + return (0); +} + +static int +mmc_wait_for_request(device_t brdev, device_t reqdev, struct mmc_request *req) +{ + struct mmc_softc *sc = device_get_softc(brdev); + + return (mmc_wait_for_req(sc, req)); +} + +static int +mmc_wait_for_cmd(struct mmc_softc *sc, struct mmc_command *cmd, int retries) +{ + struct mmc_request mreq; + + memset(&mreq, 0, sizeof(mreq)); + memset(cmd->resp, 0, sizeof(cmd->resp)); + cmd->retries = retries; + mreq.cmd = cmd; + mmc_wait_for_req(sc, &mreq); + return (cmd->error); +} + +static int +mmc_wait_for_app_cmd(struct mmc_softc *sc, uint32_t rca, + struct mmc_command *cmd, int retries) +{ + struct mmc_command appcmd; + int err = MMC_ERR_NONE, i; + + for (i = 0; i <= retries; i++) { + appcmd.opcode = MMC_APP_CMD; + appcmd.arg = rca << 16; + appcmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + appcmd.data = NULL; + mmc_wait_for_cmd(sc, &appcmd, 0); + err = appcmd.error; + if (err != MMC_ERR_NONE) + continue; + if (!(appcmd.resp[0] & R1_APP_CMD)) + return MMC_ERR_FAILED; + mmc_wait_for_cmd(sc, cmd, 0); + err = cmd->error; + if (err == MMC_ERR_NONE) + break; + } + return (err); +} + +static int +mmc_wait_for_command(struct mmc_softc *sc, uint32_t opcode, + uint32_t arg, uint32_t flags, uint32_t *resp, int retries) +{ + struct mmc_command cmd; + int err; + + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = opcode; + cmd.arg = arg; + cmd.flags = flags; + cmd.data = NULL; + err = mmc_wait_for_cmd(sc, &cmd, retries); + if (err) + return (err); + if (cmd.error) + return (cmd.error); + if (resp) { + if (flags & MMC_RSP_136) + memcpy(resp, cmd.resp, 4 * sizeof(uint32_t)); + else + *resp = cmd.resp[0]; + } + return (0); +} + +static void +mmc_idle_cards(struct mmc_softc *sc) +{ + device_t dev; + struct mmc_command cmd; + + dev = sc->dev; + mmcbr_set_chip_select(dev, cs_high); + mmcbr_update_ios(dev); + mmc_ms_delay(1); + + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = MMC_GO_IDLE_STATE; + cmd.arg = 0; + cmd.flags = MMC_RSP_NONE | MMC_CMD_BC; + cmd.data = NULL; + mmc_wait_for_cmd(sc, &cmd, 0); + mmc_ms_delay(1); + + mmcbr_set_chip_select(dev, cs_dontcare); + mmcbr_update_ios(dev); + mmc_ms_delay(1); +} + +static int +mmc_send_app_op_cond(struct mmc_softc *sc, uint32_t ocr, uint32_t *rocr) +{ + struct mmc_command cmd; + int err = MMC_ERR_NONE, i; + + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = ACMD_SD_SEND_OP_COND; + cmd.arg = ocr; + cmd.flags = MMC_RSP_R3 | MMC_CMD_BCR; + cmd.data = NULL; + + for (i = 0; i < 100; i++) { + err = mmc_wait_for_app_cmd(sc, 0, &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 (rocr && err == MMC_ERR_NONE) + *rocr = cmd.resp[0]; + return (err); +} + +static int +mmc_send_op_cond(struct mmc_softc *sc, uint32_t ocr, uint32_t *rocr) +{ + struct mmc_command cmd; + int err = MMC_ERR_NONE, i; + + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = MMC_SEND_OP_COND; + cmd.arg = ocr; + cmd.flags = MMC_RSP_R3 | MMC_CMD_BCR; + cmd.data = NULL; + + for (i = 0; i < 100; 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 (rocr && err == MMC_ERR_NONE) + *rocr = cmd.resp[0]; + return (err); +} + +static int +mmc_send_if_cond(struct mmc_softc *sc, uint8_t vhs) +{ + struct mmc_command cmd; + int err; + + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = SD_SEND_IF_COND; + cmd.arg = (vhs << 8) + 0xAA; + cmd.flags = MMC_RSP_R7 | MMC_CMD_BCR; + cmd.data = NULL; + + err = mmc_wait_for_cmd(sc, &cmd, CMD_RETRIES); + return (err); +} + +static void +mmc_power_up(struct mmc_softc *sc) +{ + device_t dev; + + dev = sc->dev; + mmcbr_set_vdd(dev, mmc_highest_voltage(mmcbr_get_host_ocr(dev))); + mmcbr_set_bus_mode(dev, opendrain); + mmcbr_set_chip_select(dev, cs_dontcare); + mmcbr_set_bus_width(dev, bus_width_1); + mmcbr_set_power_mode(dev, power_up); + mmcbr_set_clock(dev, 0); + mmcbr_update_ios(dev); + mmc_ms_delay(1); + + mmcbr_set_clock(dev, mmcbr_get_f_min(sc->dev)); + mmcbr_set_timing(dev, bus_timing_normal); + mmcbr_set_power_mode(dev, power_on); + mmcbr_update_ios(dev); + mmc_ms_delay(2); +} + +static void +mmc_power_down(struct mmc_softc *sc) +{ + device_t dev = sc->dev; + + mmcbr_set_bus_mode(dev, opendrain); + mmcbr_set_chip_select(dev, cs_dontcare); + mmcbr_set_bus_width(dev, bus_width_1); + mmcbr_set_power_mode(dev, power_off); + mmcbr_set_clock(dev, 0); + mmcbr_set_timing(dev, bus_timing_normal); + mmcbr_update_ios(dev); +} + +static int +mmc_select_card(struct mmc_softc *sc, uint16_t rca) +{ + int flags; + + flags = (rca ? MMC_RSP_R1B : MMC_RSP_NONE) | MMC_CMD_AC; + return (mmc_wait_for_command(sc, MMC_SELECT_CARD, (uint32_t)rca << 16, + flags, NULL, CMD_RETRIES)); +} + +static int +mmc_switch(struct mmc_softc *sc, uint8_t set, uint8_t index, uint8_t value) +{ + struct mmc_command cmd; + int err; + + cmd.opcode = MMC_SWITCH_FUNC; + cmd.arg = (MMC_SWITCH_FUNC_WR << 24) | + (index << 16) | + (value << 8) | + set; + cmd.flags = MMC_RSP_R1B | MMC_CMD_AC; + cmd.data = NULL; + err = mmc_wait_for_cmd(sc, &cmd, 0); + return (err); +} + +static int +mmc_sd_switch(struct mmc_softc *sc, uint8_t mode, uint8_t grp, uint8_t value, + uint8_t *res) +{ + int err; + struct mmc_command cmd; + struct mmc_data data; + + memset(&cmd, 0, sizeof(struct mmc_command)); + memset(&data, 0, sizeof(struct mmc_data)); + memset(res, 0, 64); + + cmd.opcode = SD_SWITCH_FUNC; + cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + cmd.arg = mode << 31; /* 0 - check, 1 - set */ + cmd.arg |= 0x00FFFFFF; + cmd.arg &= ~(0xF << (grp * 4)); + cmd.arg |= value << (grp * 4); + cmd.data = &data; + + data.data = res; + data.len = 64; + data.flags = MMC_DATA_READ; + + err = mmc_wait_for_cmd(sc, &cmd, CMD_RETRIES); + return (err); +} + +static int +mmc_set_card_bus_width(struct mmc_softc *sc, uint16_t rca, int width) +{ + struct mmc_command cmd; + int err; + uint8_t value; + + if (mmcbr_get_mode(sc->dev) == mode_sd) { + memset(&cmd, 0, sizeof(struct mmc_command)); + cmd.opcode = ACMD_SET_BUS_WIDTH; + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + switch (width) { + case bus_width_1: + cmd.arg = SD_BUS_WIDTH_1; + break; + case bus_width_4: + cmd.arg = SD_BUS_WIDTH_4; + break; + default: + return (MMC_ERR_INVALID); + } + err = mmc_wait_for_app_cmd(sc, rca, &cmd, CMD_RETRIES); + } else { + switch (width) { + case bus_width_1: + value = EXT_CSD_BUS_WIDTH_1; + break; + case bus_width_4: + value = EXT_CSD_BUS_WIDTH_4; + break; + case bus_width_8: + value = EXT_CSD_BUS_WIDTH_8; + break; + default: + return (MMC_ERR_INVALID); + } + err = mmc_switch(sc, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BUS_WIDTH, + value); + } + return (err); +} + +static int +mmc_set_timing(struct mmc_softc *sc, int timing) +{ + int err; + uint8_t value; + u_char switch_res[64]; + + switch (timing) { + case bus_timing_normal: + value = 0; + break; + case bus_timing_hs: + value = 1; + break; + default: + return (MMC_ERR_INVALID); + } + if (mmcbr_get_mode(sc->dev) == mode_sd) + err = mmc_sd_switch(sc, SD_SWITCH_MODE_SET, SD_SWITCH_GROUP1, + value, switch_res); + else + err = mmc_switch(sc, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_HS_TIMING, value); + return (err); +} + +static int +mmc_test_bus_width(struct mmc_softc *sc) +{ + struct mmc_command cmd; + struct mmc_data data; + int err; + uint8_t buf[8]; + uint8_t p8[8] = { 0x55, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + uint8_t p8ok[8] = { 0xAA, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + uint8_t p4[4] = { 0x5A, 0x00, 0x00, 0x00, }; + uint8_t p4ok[4] = { 0xA5, 0x00, 0x00, 0x00, }; + + if (mmcbr_get_caps(sc->dev) & MMC_CAP_8_BIT_DATA) { + mmcbr_set_bus_width(sc->dev, bus_width_8); + mmcbr_update_ios(sc->dev); + + cmd.opcode = MMC_BUSTEST_W; + cmd.arg = 0; + cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + cmd.data = &data; + + data.data = p8; + data.len = 8; + data.flags = MMC_DATA_WRITE; + mmc_wait_for_cmd(sc, &cmd, 0); + + cmd.opcode = MMC_BUSTEST_R; + cmd.arg = 0; + cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + cmd.data = &data; + + data.data = buf; + data.len = 8; + data.flags = MMC_DATA_READ; + err = mmc_wait_for_cmd(sc, &cmd, 0); + + mmcbr_set_bus_width(sc->dev, bus_width_1); + mmcbr_update_ios(sc->dev); + + if (err == MMC_ERR_NONE && memcmp(buf, p8ok, 8) == 0) + return (bus_width_8); + } + + if (mmcbr_get_caps(sc->dev) & MMC_CAP_4_BIT_DATA) { + mmcbr_set_bus_width(sc->dev, bus_width_4); + mmcbr_update_ios(sc->dev); + + cmd.opcode = MMC_BUSTEST_W; + cmd.arg = 0; + cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + cmd.data = &data; + + data.data = p4; + data.len = 4; + data.flags = MMC_DATA_WRITE; + mmc_wait_for_cmd(sc, &cmd, 0); + + cmd.opcode = MMC_BUSTEST_R; + cmd.arg = 0; + cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + cmd.data = &data; + + data.data = buf; + data.len = 4; + data.flags = MMC_DATA_READ; + err = mmc_wait_for_cmd(sc, &cmd, 0); + + mmcbr_set_bus_width(sc->dev, bus_width_1); + mmcbr_update_ios(sc->dev); + + if (err == MMC_ERR_NONE && memcmp(buf, p4ok, 4) == 0) + return (bus_width_4); + } + return (bus_width_1); +} + +static uint32_t +mmc_get_bits(uint32_t *bits, int bit_len, int start, int size) +{ + const int i = (bit_len / 32) - (start / 32) - 1; + const int shift = start & 31; + uint32_t retval = bits[i] >> shift; + if (size + shift > 32) + retval |= bits[i - 1] << (32 - shift); + return (retval & ((1 << size) - 1)); +} + +static void +mmc_decode_cid_sd(uint32_t *raw_cid, struct mmc_cid *cid) +{ + int i; + + /* There's no version info, so we take it on faith */ + memset(cid, 0, sizeof(*cid)); + cid->mid = mmc_get_bits(raw_cid, 128, 120, 8); + cid->oid = mmc_get_bits(raw_cid, 128, 104, 16); + for (i = 0; i < 5; i++) + cid->pnm[i] = mmc_get_bits(raw_cid, 128, 96 - i * 8, 8); + cid->pnm[5] = 0; + cid->prv = mmc_get_bits(raw_cid, 128, 56, 8); + cid->psn = mmc_get_bits(raw_cid, 128, 24, 32); + cid->mdt_year = mmc_get_bits(raw_cid, 128, 12, 8) + 2000; + cid->mdt_month = mmc_get_bits(raw_cid, 128, 8, 4); +} + +static void +mmc_decode_cid_mmc(uint32_t *raw_cid, struct mmc_cid *cid) +{ + int i; + + /* There's no version info, so we take it on faith */ + memset(cid, 0, sizeof(*cid)); + cid->mid = mmc_get_bits(raw_cid, 128, 120, 8); + cid->oid = mmc_get_bits(raw_cid, 128, 104, 8); + for (i = 0; i < 6; i++) + cid->pnm[i] = mmc_get_bits(raw_cid, 128, 96 - i * 8, 8); + cid->pnm[6] = 0; + cid->prv = mmc_get_bits(raw_cid, 128, 48, 8); + cid->psn = mmc_get_bits(raw_cid, 128, 16, 32); + cid->mdt_month = mmc_get_bits(raw_cid, 128, 12, 4); + cid->mdt_year = mmc_get_bits(raw_cid, 128, 8, 4) + 1997; +} + +static const int exp[8] = { + 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000 +}; +static const int mant[16] = { + 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80 +}; +static const int cur_min[8] = { + 500, 1000, 5000, 10000, 25000, 35000, 60000, 100000 +}; +static const int cur_max[8] = { + 1000, 5000, 10000, 25000, 35000, 45000, 800000, 200000 +}; + +static void +mmc_decode_csd_sd(uint32_t *raw_csd, struct mmc_csd *csd) +{ + int v; + int m; + int e; + + memset(csd, 0, sizeof(*csd)); + csd->csd_structure = v = mmc_get_bits(raw_csd, 128, 126, 2); + if (v == 0) { + m = mmc_get_bits(raw_csd, 128, 115, 4); + e = mmc_get_bits(raw_csd, 128, 112, 3); + csd->tacc = exp[e] * mant[m] + 9 / 10; + csd->nsac = mmc_get_bits(raw_csd, 128, 104, 8) * 100; + m = mmc_get_bits(raw_csd, 128, 99, 4); + e = mmc_get_bits(raw_csd, 128, 96, 3); + csd->tran_speed = exp[e] * 10000 * mant[m]; + csd->ccc = mmc_get_bits(raw_csd, 128, 84, 12); + csd->read_bl_len = 1 << mmc_get_bits(raw_csd, 128, 80, 4); + csd->read_bl_partial = mmc_get_bits(raw_csd, 128, 79, 1); + csd->write_blk_misalign = mmc_get_bits(raw_csd, 128, 78, 1); + csd->read_blk_misalign = mmc_get_bits(raw_csd, 128, 77, 1); + csd->dsr_imp = mmc_get_bits(raw_csd, 128, 76, 1); + csd->vdd_r_curr_min = cur_min[mmc_get_bits(raw_csd, 128, 59, 3)]; + csd->vdd_r_curr_max = cur_max[mmc_get_bits(raw_csd, 128, 56, 3)]; + csd->vdd_w_curr_min = cur_min[mmc_get_bits(raw_csd, 128, 53, 3)]; + csd->vdd_w_curr_max = cur_max[mmc_get_bits(raw_csd, 128, 50, 3)]; + m = mmc_get_bits(raw_csd, 128, 62, 12); + e = mmc_get_bits(raw_csd, 128, 47, 3); + csd->capacity = ((1 + m) << (e + 2)) * csd->read_bl_len; + csd->erase_blk_en = mmc_get_bits(raw_csd, 128, 46, 1); + csd->erase_sector = mmc_get_bits(raw_csd, 128, 39, 7) + 1; + csd->wp_grp_size = mmc_get_bits(raw_csd, 128, 32, 7); + csd->wp_grp_enable = mmc_get_bits(raw_csd, 128, 31, 1); + csd->r2w_factor = 1 << mmc_get_bits(raw_csd, 128, 26, 3); + csd->write_bl_len = 1 << mmc_get_bits(raw_csd, 128, 22, 4); + csd->write_bl_partial = mmc_get_bits(raw_csd, 128, 21, 1); + } else if (v == 1) { + m = mmc_get_bits(raw_csd, 128, 115, 4); + e = mmc_get_bits(raw_csd, 128, 112, 3); + csd->tacc = exp[e] * mant[m] + 9 / 10; + csd->nsac = mmc_get_bits(raw_csd, 128, 104, 8) * 100; + m = mmc_get_bits(raw_csd, 128, 99, 4); + e = mmc_get_bits(raw_csd, 128, 96, 3); + csd->tran_speed = exp[e] * 10000 * mant[m]; + csd->ccc = mmc_get_bits(raw_csd, 128, 84, 12); + csd->read_bl_len = 1 << mmc_get_bits(raw_csd, 128, 80, 4); + csd->read_bl_partial = mmc_get_bits(raw_csd, 128, 79, 1); + csd->write_blk_misalign = mmc_get_bits(raw_csd, 128, 78, 1); + csd->read_blk_misalign = mmc_get_bits(raw_csd, 128, 77, 1); + csd->dsr_imp = mmc_get_bits(raw_csd, 128, 76, 1); + csd->capacity = ((uint64_t)mmc_get_bits(raw_csd, 128, 48, 22) + 1) * + 512 * 1024; + csd->erase_blk_en = mmc_get_bits(raw_csd, 128, 46, 1); + csd->erase_sector = mmc_get_bits(raw_csd, 128, 39, 7) + 1; + csd->wp_grp_size = mmc_get_bits(raw_csd, 128, 32, 7); + csd->wp_grp_enable = mmc_get_bits(raw_csd, 128, 31, 1); + csd->r2w_factor = 1 << mmc_get_bits(raw_csd, 128, 26, 3); + csd->write_bl_len = 1 << mmc_get_bits(raw_csd, 128, 22, 4); + csd->write_bl_partial = mmc_get_bits(raw_csd, 128, 21, 1); + } else + panic("unknown SD CSD version"); +} + +static void +mmc_decode_csd_mmc(uint32_t *raw_csd, struct mmc_csd *csd) +{ + int m; + int e; + + memset(csd, 0, sizeof(*csd)); + csd->csd_structure = mmc_get_bits(raw_csd, 128, 126, 2); + csd->spec_vers = mmc_get_bits(raw_csd, 128, 122, 4); + m = mmc_get_bits(raw_csd, 128, 115, 4); + e = mmc_get_bits(raw_csd, 128, 112, 3); + csd->tacc = exp[e] * mant[m] + 9 / 10; + csd->nsac = mmc_get_bits(raw_csd, 128, 104, 8) * 100; + m = mmc_get_bits(raw_csd, 128, 99, 4); + e = mmc_get_bits(raw_csd, 128, 96, 3); + csd->tran_speed = exp[e] * 10000 * mant[m]; + csd->ccc = mmc_get_bits(raw_csd, 128, 84, 12); + csd->read_bl_len = 1 << mmc_get_bits(raw_csd, 128, 80, 4); + csd->read_bl_partial = mmc_get_bits(raw_csd, 128, 79, 1); + csd->write_blk_misalign = mmc_get_bits(raw_csd, 128, 78, 1); + csd->read_blk_misalign = mmc_get_bits(raw_csd, 128, 77, 1); + csd->dsr_imp = mmc_get_bits(raw_csd, 128, 76, 1); + csd->vdd_r_curr_min = cur_min[mmc_get_bits(raw_csd, 128, 59, 3)]; + csd->vdd_r_curr_max = cur_max[mmc_get_bits(raw_csd, 128, 56, 3)]; + csd->vdd_w_curr_min = cur_min[mmc_get_bits(raw_csd, 128, 53, 3)]; + csd->vdd_w_curr_max = cur_max[mmc_get_bits(raw_csd, 128, 50, 3)]; + m = mmc_get_bits(raw_csd, 128, 62, 12); + e = mmc_get_bits(raw_csd, 128, 47, 3); + csd->capacity = ((1 + m) << (e + 2)) * csd->read_bl_len; + csd->erase_blk_en = 0; + csd->erase_sector = (mmc_get_bits(raw_csd, 128, 42, 5) + 1) * + (mmc_get_bits(raw_csd, 128, 37, 5) + 1); + csd->wp_grp_size = mmc_get_bits(raw_csd, 128, 32, 5); + csd->wp_grp_enable = mmc_get_bits(raw_csd, 128, 31, 1); + csd->r2w_factor = 1 << mmc_get_bits(raw_csd, 128, 26, 3); + csd->write_bl_len = 1 << mmc_get_bits(raw_csd, 128, 22, 4); + csd->write_bl_partial = mmc_get_bits(raw_csd, 128, 21, 1); +} + +static void +mmc_app_decode_scr(uint32_t *raw_scr, struct mmc_scr *scr) +{ + unsigned int scr_struct; + + memset(scr, 0, sizeof(*scr)); + + scr_struct = mmc_get_bits(raw_scr, 64, 60, 4); + if (scr_struct != 0) { + kprintf("Unrecognised SCR structure version %d\n", + scr_struct); + return; + } + scr->sda_vsn = mmc_get_bits(raw_scr, 64, 56, 4); + scr->bus_widths = mmc_get_bits(raw_scr, 64, 48, 4); +} + +static void +mmc_app_decode_sd_status(uint32_t *raw_sd_status, + struct mmc_sd_status *sd_status) +{ + + memset(sd_status, 0, sizeof(*sd_status)); + + sd_status->bus_width = mmc_get_bits(raw_sd_status, 512, 510, 2); + sd_status->secured_mode = mmc_get_bits(raw_sd_status, 512, 509, 1); + sd_status->card_type = mmc_get_bits(raw_sd_status, 512, 480, 16); + sd_status->prot_area = mmc_get_bits(raw_sd_status, 512, 448, 12); + sd_status->speed_class = mmc_get_bits(raw_sd_status, 512, 440, 8); + sd_status->perf_move = mmc_get_bits(raw_sd_status, 512, 432, 8); + sd_status->au_size = mmc_get_bits(raw_sd_status, 512, 428, 4); + sd_status->erase_size = mmc_get_bits(raw_sd_status, 512, 408, 16); + sd_status->erase_timeout = mmc_get_bits(raw_sd_status, 512, 402, 6); + sd_status->erase_offset = mmc_get_bits(raw_sd_status, 512, 400, 2); +} + +static int +mmc_all_send_cid(struct mmc_softc *sc, uint32_t *rawcid) +{ + struct mmc_command cmd; + int err; + + cmd.opcode = MMC_ALL_SEND_CID; + cmd.arg = 0; + cmd.flags = MMC_RSP_R2 | MMC_CMD_BCR; + cmd.data = NULL; + err = mmc_wait_for_cmd(sc, &cmd, 0); + memcpy(rawcid, cmd.resp, 4 * sizeof(uint32_t)); + return (err); +} + +static int +mmc_send_csd(struct mmc_softc *sc, uint16_t rca, uint32_t *rawcid) +{ + struct mmc_command cmd; + int err; + + cmd.opcode = MMC_SEND_CSD; + cmd.arg = rca << 16; + cmd.flags = MMC_RSP_R2 | MMC_CMD_BCR; + cmd.data = NULL; + err = mmc_wait_for_cmd(sc, &cmd, 0); + memcpy(rawcid, cmd.resp, 4 * sizeof(uint32_t)); + return (err); +} + +static int +mmc_app_send_scr(struct mmc_softc *sc, uint16_t rca, uint32_t *rawscr) +{ + int err; + struct mmc_command cmd; + struct mmc_data data; + + memset(&cmd, 0, sizeof(struct mmc_command)); + memset(&data, 0, sizeof(struct mmc_data)); + + memset(rawscr, 0, 8); + cmd.opcode = ACMD_SEND_SCR; + cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + cmd.arg = 0; + cmd.data = &data; + + data.data = rawscr; + data.len = 8; + data.flags = MMC_DATA_READ; + + err = mmc_wait_for_app_cmd(sc, rca, &cmd, CMD_RETRIES); + rawscr[0] = be32toh(rawscr[0]); + rawscr[1] = be32toh(rawscr[1]); + return (err); +} + +static int +mmc_send_ext_csd(struct mmc_softc *sc, uint8_t *rawextcsd) +{ + int err; + struct mmc_command cmd; + struct mmc_data data; + + memset(&cmd, 0, sizeof(struct mmc_command)); + memset(&data, 0, sizeof(struct mmc_data)); + + memset(rawextcsd, 0, 512); + cmd.opcode = MMC_SEND_EXT_CSD; + cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + cmd.arg = 0; + cmd.data = &data; + + data.data = rawextcsd; + data.len = 512; + data.flags = MMC_DATA_READ; + + err = mmc_wait_for_cmd(sc, &cmd, CMD_RETRIES); + return (err); +} + +static int +mmc_app_sd_status(struct mmc_softc *sc, uint16_t rca, uint32_t *rawsdstatus) +{ + int err, i; + struct mmc_command cmd; + struct mmc_data data; + + memset(&cmd, 0, sizeof(struct mmc_command)); + memset(&data, 0, sizeof(struct mmc_data)); + + memset(rawsdstatus, 0, 64); + cmd.opcode = ACMD_SD_STATUS; + cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + cmd.arg = 0; + cmd.data = &data; + + data.data = rawsdstatus; + data.len = 64; + data.flags = MMC_DATA_READ; + + err = mmc_wait_for_app_cmd(sc, rca, &cmd, CMD_RETRIES); + for (i = 0; i < 16; i++) + rawsdstatus[i] = be32toh(rawsdstatus[i]); + return (err); +} + +static int +mmc_set_relative_addr(struct mmc_softc *sc, uint16_t resp) +{ + struct mmc_command cmd; + int err; + + cmd.opcode = MMC_SET_RELATIVE_ADDR; + cmd.arg = resp << 16; + cmd.flags = MMC_RSP_R6 | MMC_CMD_BCR; + cmd.data = NULL; + err = mmc_wait_for_cmd(sc, &cmd, 0); + return (err); +} + +static int +mmc_send_relative_addr(struct mmc_softc *sc, uint32_t *resp) +{ + struct mmc_command cmd; + int err; + + cmd.opcode = SD_SEND_RELATIVE_ADDR; + cmd.arg = 0; + cmd.flags = MMC_RSP_R6 | MMC_CMD_BCR; + cmd.data = NULL; + err = mmc_wait_for_cmd(sc, &cmd, 0); + *resp = cmd.resp[0]; + return (err); +} + +static void +mmc_log_card(device_t dev, struct mmc_ivars *ivar, int newcard) +{ + device_printf(dev, "Card at relative address %d%s:\n", + ivar->rca, newcard ? " added" : ""); + device_printf(dev, " card: %s%s (0x%x/0x%x/\"%s\" rev %d.%d " + "m/d %02d.%04d s/n %08x)\n", + ivar->mode == mode_sd ? "SD" : "MMC", + ivar->high_cap ? " High Capacity" : "", + ivar->cid.mid, ivar->cid.oid, + ivar->cid.pnm, ivar->cid.prv >> 4, ivar->cid.prv & 0x0f, + ivar->cid.mdt_month, ivar->cid.mdt_year, ivar->cid.psn); + device_printf(dev, " bus: %ubit, %uMHz%s\n", + (ivar->bus_width == bus_width_1 ? 1 : + (ivar->bus_width == bus_width_4 ? 4 : 8)), + (ivar->timing == bus_timing_hs ? + ivar->hs_tran_speed : ivar->tran_speed) / 1000000, + ivar->timing == bus_timing_hs ? ", high speed timing" : ""); + device_printf(dev, " memory: %u blocks, erase sector %u blocks%s\n", + ivar->sec_count, ivar->erase_sector, + ivar->read_only ? ", read-only" : ""); +} + +static void +mmc_discover_cards(struct mmc_softc *sc) +{ + struct mmc_ivars *ivar = NULL; + device_t *devlist; + int err, i, devcount, newcard; + uint32_t raw_cid[4]; + uint32_t resp, sec_count; + device_t child; + uint16_t rca = 2; + u_char switch_res[64]; + + if (bootverbose || mmc_debug) + device_printf(sc->dev, "Probing cards\n"); + while (1) { + err = mmc_all_send_cid(sc, raw_cid); + if (err == MMC_ERR_TIMEOUT) + break; + if (err != MMC_ERR_NONE) { + device_printf(sc->dev, "Error reading CID %d\n", err); + break; + } + newcard = 1; + if ((err = device_get_children(sc->dev, &devlist, &devcount)) != 0) + return; + for (i = 0; i < devcount; i++) { + ivar = device_get_ivars(devlist[i]); + if (memcmp(ivar->raw_cid, raw_cid, sizeof(raw_cid)) == 0) { + newcard = 0; + break; + } + } + kfree(devlist, M_TEMP); + if (bootverbose || mmc_debug) { + device_printf(sc->dev, "%sard detected (CID %08x%08x%08x%08x)\n", + newcard ? "New c" : "C", + raw_cid[0], raw_cid[1], raw_cid[2], raw_cid[3]); + } + if (newcard) { + ivar = kmalloc(sizeof(struct mmc_ivars), M_DEVBUF, + M_WAITOK | M_ZERO); + if (!ivar) + return; + memcpy(ivar->raw_cid, raw_cid, sizeof(raw_cid)); + } + if (mmcbr_get_ro(sc->dev)) + ivar->read_only = 1; + ivar->bus_width = bus_width_1; + ivar->timing = bus_timing_normal; + ivar->mode = mmcbr_get_mode(sc->dev); + if (ivar->mode == mode_sd) { + mmc_decode_cid_sd(ivar->raw_cid, &ivar->cid); + mmc_send_relative_addr(sc, &resp); + ivar->rca = resp >> 16; + /* Get card CSD. */ + mmc_send_csd(sc, ivar->rca, ivar->raw_csd); + mmc_decode_csd_sd(ivar->raw_csd, &ivar->csd); + ivar->sec_count = ivar->csd.capacity / MMC_SECTOR_SIZE; + if (ivar->csd.csd_structure > 0) + ivar->high_cap = 1; + ivar->tran_speed = ivar->csd.tran_speed; + ivar->erase_sector = ivar->csd.erase_sector * + ivar->csd.write_bl_len / MMC_SECTOR_SIZE; + /* Get card SCR. Card must be selected to fetch it. */ + mmc_select_card(sc, ivar->rca); + mmc_app_send_scr(sc, ivar->rca, ivar->raw_scr); + mmc_app_decode_scr(ivar->raw_scr, &ivar->scr); + /* Get card switch capabilities (command class 10). */ + if ((ivar->scr.sda_vsn >= 1) && + (ivar->csd.ccc & (1<<10))) { + mmc_sd_switch(sc, SD_SWITCH_MODE_CHECK, + SD_SWITCH_GROUP1, SD_SWITCH_NOCHANGE, + switch_res); + if (switch_res[13] & 2) { + ivar->timing = bus_timing_hs; + ivar->hs_tran_speed = SD_MAX_HS; + } + } + mmc_app_sd_status(sc, ivar->rca, ivar->raw_sd_status); + mmc_app_decode_sd_status(ivar->raw_sd_status, + &ivar->sd_status); + if (ivar->sd_status.au_size != 0) { + ivar->erase_sector = + 16 << ivar->sd_status.au_size; + } + mmc_select_card(sc, 0); + /* Find max supported bus width. */ + if ((mmcbr_get_caps(sc->dev) & MMC_CAP_4_BIT_DATA) && + (ivar->scr.bus_widths & SD_SCR_BUS_WIDTH_4)) + ivar->bus_width = bus_width_4; + if (bootverbose || mmc_debug) + mmc_log_card(sc->dev, ivar, newcard); + if (newcard) { + /* Add device. */ + child = device_add_child(sc->dev, NULL, -1); + device_set_ivars(child, ivar); + } + return; + } + mmc_decode_cid_mmc(ivar->raw_cid, &ivar->cid); + ivar->rca = rca++; + mmc_set_relative_addr(sc, ivar->rca); + /* Get card CSD. */ + mmc_send_csd(sc, ivar->rca, ivar->raw_csd); + mmc_decode_csd_mmc(ivar->raw_csd, &ivar->csd); + ivar->sec_count = ivar->csd.capacity / MMC_SECTOR_SIZE; + ivar->tran_speed = ivar->csd.tran_speed; + ivar->erase_sector = ivar->csd.erase_sector * + ivar->csd.write_bl_len / MMC_SECTOR_SIZE; + /* Only MMC >= 4.x cards support EXT_CSD. */ + if (ivar->csd.spec_vers >= 4) { + /* Card must be selected to fetch EXT_CSD. */ + mmc_select_card(sc, ivar->rca); + mmc_send_ext_csd(sc, ivar->raw_ext_csd); + /* Handle extended capacity from EXT_CSD */ + sec_count = ivar->raw_ext_csd[EXT_CSD_SEC_CNT] + + (ivar->raw_ext_csd[EXT_CSD_SEC_CNT + 1] << 8) + + (ivar->raw_ext_csd[EXT_CSD_SEC_CNT + 2] << 16) + + (ivar->raw_ext_csd[EXT_CSD_SEC_CNT + 3] << 24); + if (sec_count != 0) { + ivar->sec_count = sec_count; + ivar->high_cap = 1; + } + /* Get card speed in high speed mode. */ + ivar->timing = bus_timing_hs; + if (ivar->raw_ext_csd[EXT_CSD_CARD_TYPE] + & EXT_CSD_CARD_TYPE_52) + ivar->hs_tran_speed = MMC_TYPE_52_MAX_HS; + else if (ivar->raw_ext_csd[EXT_CSD_CARD_TYPE] + & EXT_CSD_CARD_TYPE_26) + ivar->hs_tran_speed = MMC_TYPE_26_MAX_HS; + else + ivar->hs_tran_speed = ivar->tran_speed; + /* Find max supported bus width. */ + ivar->bus_width = mmc_test_bus_width(sc); + mmc_select_card(sc, 0); + /* Handle HC erase sector size. */ + if (ivar->raw_ext_csd[EXT_CSD_ERASE_GRP_SIZE] != 0) { + ivar->erase_sector = 1024 * + ivar->raw_ext_csd[EXT_CSD_ERASE_GRP_SIZE]; + mmc_switch(sc, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_ERASE_GRP_DEF, 1); + } + } else { + ivar->bus_width = bus_width_1; + ivar->timing = bus_timing_normal; + } + if (bootverbose || mmc_debug) + mmc_log_card(sc->dev, ivar, newcard); + if (newcard) { + /* Add device. */ + child = device_add_child(sc->dev, NULL, -1); + device_set_ivars(child, ivar); + } + } +} + +static void +mmc_rescan_cards(struct mmc_softc *sc) +{ + struct mmc_ivars *ivar = NULL; + device_t *devlist; + int err, i, devcount; + + if ((err = device_get_children(sc->dev, &devlist, &devcount)) != 0) + return; + for (i = 0; i < devcount; i++) { + ivar = device_get_ivars(devlist[i]); + if (mmc_select_card(sc, ivar->rca)) { + if (bootverbose || mmc_debug) + device_printf(sc->dev, "Card at relative address %d lost.\n", + ivar->rca); + device_delete_child(sc->dev, devlist[i]); + kfree(ivar, M_DEVBUF); + } + } + kfree(devlist, M_TEMP); + mmc_select_card(sc, 0); +} + +static int +mmc_delete_cards(struct mmc_softc *sc) +{ + struct mmc_ivars *ivar; + device_t *devlist; + int err, i, devcount; + + if ((err = device_get_children(sc->dev, &devlist, &devcount)) != 0) + return (err); + for (i = 0; i < devcount; i++) { + ivar = device_get_ivars(devlist[i]); + if (bootverbose || mmc_debug) + device_printf(sc->dev, "Card at relative address %d deleted.\n", + ivar->rca); + device_delete_child(sc->dev, devlist[i]); + kfree(ivar, M_DEVBUF); + } + kfree(devlist, M_TEMP); + return (0); +} + +static void +mmc_go_discovery(struct mmc_softc *sc) +{ + uint32_t ocr; + device_t dev; + int err; + + dev = sc->dev; + if (mmcbr_get_power_mode(dev) != power_on) { + /* + * First, try SD modes + */ + mmcbr_set_mode(dev, mode_sd); + mmc_power_up(sc); + mmcbr_set_bus_mode(dev, pushpull); + if (bootverbose || mmc_debug) + device_printf(sc->dev, "Probing bus\n"); + mmc_idle_cards(sc); + err = mmc_send_if_cond(sc, 1); + if ((bootverbose || mmc_debug) && err == 0) + device_printf(sc->dev, "SD 2.0 interface conditions: OK\n"); + if (mmc_send_app_op_cond(sc, err ? 0 : MMC_OCR_CCS, &ocr) != + MMC_ERR_NONE) { + 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 (bootverbose || mmc_debug) + device_printf(sc->dev, "MMC probe: failed\n"); + ocr = 0; /* Failed both, powerdown. */ + } else if (bootverbose || mmc_debug) + device_printf(sc->dev, + "MMC probe: OK (OCR: 0x%08x)\n", ocr); + } else if (bootverbose || mmc_debug) + device_printf(sc->dev, "SD probe: OK (OCR: 0x%08x)\n", ocr); + + mmcbr_set_ocr(dev, mmc_select_vdd(sc, ocr)); + if (mmcbr_get_ocr(dev) != 0) + mmc_idle_cards(sc); + } else { + mmcbr_set_bus_mode(dev, opendrain); + mmcbr_set_clock(dev, mmcbr_get_f_min(dev)); + mmcbr_update_ios(dev); + /* XXX recompute vdd based on new cards? */ + } + /* + * Make sure that we have a mutually agreeable voltage to at least + * one card on the bus. + */ + if (bootverbose || mmc_debug) + device_printf(sc->dev, "Current OCR: 0x%08x\n", mmcbr_get_ocr(dev)); + if (mmcbr_get_ocr(dev) == 0) { + mmc_delete_cards(sc); + mmc_power_down(sc); + return; + } + /* + * 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); + } else + mmc_send_op_cond(sc, mmcbr_get_ocr(dev), NULL); + mmc_discover_cards(sc); + mmc_rescan_cards(sc); + + mmcbr_set_bus_mode(dev, pushpull); + mmcbr_update_ios(dev); + mmc_calculate_clock(sc); + bus_generic_attach(dev); +/* mmc_update_children_sysctl(dev);*/ +} + +static int +mmc_calculate_clock(struct mmc_softc *sc) +{ + int max_dtr, max_hs_dtr, max_timing; + int nkid, i, f_min, f_max; + device_t *kids; + struct mmc_ivars *ivar; + + f_min = mmcbr_get_f_min(sc->dev); + f_max = mmcbr_get_f_max(sc->dev); + max_dtr = max_hs_dtr = f_max; + if ((mmcbr_get_caps(sc->dev) & MMC_CAP_HSPEED)) + max_timing = bus_timing_hs; + else + max_timing = bus_timing_normal; + if (device_get_children(sc->dev, &kids, &nkid) != 0) + panic("can't get children"); + for (i = 0; i < nkid; i++) { + ivar = device_get_ivars(kids[i]); + if (ivar->timing < max_timing) + max_timing = ivar->timing; + if (ivar->tran_speed < max_dtr) + max_dtr = ivar->tran_speed; + if (ivar->hs_tran_speed < max_hs_dtr) + max_hs_dtr = ivar->hs_tran_speed; + } + for (i = 0; i < nkid; i++) { + ivar = device_get_ivars(kids[i]); + if (ivar->timing == bus_timing_normal) + continue; + mmc_select_card(sc, ivar->rca); + mmc_set_timing(sc, max_timing); + } + mmc_select_card(sc, 0); + kfree(kids, M_TEMP); + if (max_timing == bus_timing_hs) + max_dtr = max_hs_dtr; + if (bootverbose || mmc_debug) { + device_printf(sc->dev, + "setting transfer rate to %d.%03dMHz%s\n", + max_dtr / 1000000, (max_dtr / 1000) % 1000, + max_timing == bus_timing_hs ? " (high speed timing)" : ""); + } + mmcbr_set_timing(sc->dev, max_timing); + mmcbr_set_clock(sc->dev, max_dtr); + mmcbr_update_ios(sc->dev); + return max_dtr; +} + +static void +mmc_scan(struct mmc_softc *sc) +{ + device_t dev = sc->dev; + + mmc_acquire_bus(dev, dev); + mmc_go_discovery(sc); + mmc_release_bus(dev, dev); +} + +static int +mmc_read_ivar(device_t bus, device_t child, int which, uintptr_t *result) +{ + struct mmc_ivars *ivar = device_get_ivars(child); + + switch (which) { + default: + return (EINVAL); + case MMC_IVAR_DSR_IMP: + *(int *)result = ivar->csd.dsr_imp; + break; + case MMC_IVAR_MEDIA_SIZE: + *(off_t *)result = ivar->sec_count; + break; + case MMC_IVAR_RCA: + *(int *)result = ivar->rca; + break; + case MMC_IVAR_SECTOR_SIZE: + *(int *)result = MMC_SECTOR_SIZE; + break; + case MMC_IVAR_TRAN_SPEED: + *(int *)result = mmcbr_get_clock(bus); + break; + case MMC_IVAR_READ_ONLY: + *(int *)result = ivar->read_only; + break; + case MMC_IVAR_HIGH_CAP: + *(int *)result = ivar->high_cap; + break; + case MMC_IVAR_CARD_TYPE: + *(int *)result = ivar->mode; + break; + case MMC_IVAR_BUS_WIDTH: + *(int *)result = ivar->bus_width; + break; + case MMC_IVAR_ERASE_SECTOR: + *(int *)result = ivar->erase_sector; + break; + case MMC_IVAR_MAX_DATA: + *(int *)result = mmcbr_get_max_data(bus); + break; + } + return (0); +} + +static int +mmc_write_ivar(device_t bus, device_t child, int which, uintptr_t value) +{ + /* + * None are writable ATM + */ + return (EINVAL); +} + + +static void +mmc_delayed_attach(void *xsc) +{ + struct mmc_softc *sc = xsc; + + mmc_scan(sc); + config_intrhook_disestablish(&sc->config_intrhook); +} + +static device_method_t mmc_methods[] = { + /* device_if */ + DEVMETHOD(device_probe, mmc_probe), + DEVMETHOD(device_attach, mmc_attach), + DEVMETHOD(device_detach, mmc_detach), + DEVMETHOD(device_suspend, mmc_suspend), + DEVMETHOD(device_resume, mmc_resume), + + /* Bus interface */ + DEVMETHOD(bus_read_ivar, mmc_read_ivar), + DEVMETHOD(bus_write_ivar, mmc_write_ivar), + + /* MMC Bus interface */ + DEVMETHOD(mmcbus_wait_for_request, mmc_wait_for_request), + DEVMETHOD(mmcbus_acquire_bus, mmc_acquire_bus), + DEVMETHOD(mmcbus_release_bus, mmc_release_bus), + + {0, 0}, +}; + +static driver_t mmc_driver = { + "mmc", + mmc_methods, + sizeof(struct mmc_softc), +}; +static devclass_t mmc_devclass; + + +DRIVER_MODULE(mmc, at91_mci, mmc_driver, mmc_devclass, NULL, NULL); +DRIVER_MODULE(mmc, sdhci, mmc_driver, mmc_devclass, NULL, NULL); diff --git a/sys/bus/mmc/mmcbr_if.m b/sys/bus/mmc/mmcbr_if.m new file mode 100644 index 0000000000..56a0eeadd7 --- /dev/null +++ b/sys/bus/mmc/mmcbr_if.m @@ -0,0 +1,112 @@ +#- +# Copyright (c) 2006 M. Warner Losh +# 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. +# +# Portions of this software may have been developed with reference to +# the SD Simplified Specification. The following disclaimer may apply: +# +# The following conditions apply to the release of the simplified +# specification ("Simplified Specification") by the SD Card Association and +# the SD Group. The Simplified Specification is a subset of the complete SD +# Specification which is owned by the SD Card Association and the SD +# Group. This Simplified Specification is provided on a non-confidential +# basis subject to the disclaimers below. Any implementation of the +# Simplified Specification may require a license from the SD Card +# Association, SD Group, SD-3C LLC or other third parties. +# +# Disclaimers: +# +# The information contained in the Simplified Specification is presented only +# as a standard specification for SD Cards and SD Host/Ancillary products and +# is provided "AS-IS" without any representations or warranties of any +# kind. No responsibility is assumed by the SD Group, SD-3C LLC or the SD +# Card Association for any damages, any infringements of patents or other +# right of the SD Group, SD-3C LLC, the SD Card Association or any third +# parties, which may result from its use. No license is granted by +# implication, estoppel or otherwise under any patent or other rights of the +# SD Group, SD-3C LLC, the SD Card Association or any third party. Nothing +# herein shall be construed as an obligation by the SD Group, the SD-3C LLC +# or the SD Card Association to disclose or distribute any technical +# information, know-how or other confidential information to any third party. +# +# $FreeBSD: src/sys/dev/mmc/mmcbr_if.m,v 1.2 2007/05/26 05:23:36 imp Exp $ +# + +#include +#include +#include +#include + +# +# This is the interface that a mmc bridge chip gives to the mmc bus +# that attaches to the mmc bridge. +# + +INTERFACE mmcbr; + +# +# Called by the mmcbus to setup the IO pins correctly, the voltage to use +# for the card, the type of selects, power modes and bus width. +# +METHOD int update_ios { + device_t brdev; + device_t reqdev; +}; + +# +# Called by the mmcbus or its children to schedule a mmc request. These +# requests are queued. Time passes. The bridge then gets notification +# of the status of request, who then notifies the requesting device via +# the xfer_done mmcbus method. +# +METHOD int request { + device_t brdev; + device_t reqdev; + struct mmc_request *req; +}; + +# +# Called by mmcbus to get the read only status bits. +# +METHOD int get_ro { + device_t brdev; + device_t reqdev; +}; + +# +# Claim the current bridge, blocking the current thread until the host +# is no longer busy. +# +METHOD int acquire_host { + device_t brdev; + device_t reqdev; +} + +# +# Release the current bridge. +# +METHOD int release_host { + device_t brdev; + device_t reqdev; +} diff --git a/sys/bus/mmc/mmcbrvar.h b/sys/bus/mmc/mmcbrvar.h new file mode 100644 index 0000000000..c87468b24c --- /dev/null +++ b/sys/bus/mmc/mmcbrvar.h @@ -0,0 +1,112 @@ +/*- + * Copyright (c) 2006 Bernd Walter. All rights reserved. + * Copyright (c) 2006 M. Warner Losh. 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 ``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 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. + * + * Portions of this software may have been developed with reference to + * the SD Simplified Specification. The following disclaimer may apply: + * + * The following conditions apply to the release of the simplified + * specification ("Simplified Specification") by the SD Card Association and + * the SD Group. The Simplified Specification is a subset of the complete SD + * Specification which is owned by the SD Card Association and the SD + * Group. This Simplified Specification is provided on a non-confidential + * basis subject to the disclaimers below. Any implementation of the + * Simplified Specification may require a license from the SD Card + * Association, SD Group, SD-3C LLC or other third parties. + * + * Disclaimers: + * + * The information contained in the Simplified Specification is presented only + * as a standard specification for SD Cards and SD Host/Ancillary products and + * is provided "AS-IS" without any representations or warranties of any + * kind. No responsibility is assumed by the SD Group, SD-3C LLC or the SD + * Card Association for any damages, any infringements of patents or other + * right of the SD Group, SD-3C LLC, the SD Card Association or any third + * parties, which may result from its use. No license is granted by + * implication, estoppel or otherwise under any patent or other rights of the + * SD Group, SD-3C LLC, the SD Card Association or any third party. Nothing + * herein shall be construed as an obligation by the SD Group, the SD-3C LLC + * or the SD Card Association to disclose or distribute any technical + * information, know-how or other confidential information to any third party. + * + * "$FreeBSD: src/sys/dev/mmc/mmcbrvar.h,v 1.6 2008/10/29 20:01:26 mav Exp $" + */ + +#ifndef BUS_MMC_MMCBRVAR_H +#define BUS_MMC_MMCBRVAR_H + +#include +#include "mmcbr_if.h" + +enum mmcbr_device_ivars { + MMCBR_IVAR_BUS_MODE, + MMCBR_IVAR_BUS_WIDTH, + MMCBR_IVAR_CHIP_SELECT, + MMCBR_IVAR_CLOCK, + MMCBR_IVAR_F_MIN, + MMCBR_IVAR_F_MAX, + MMCBR_IVAR_HOST_OCR, + MMCBR_IVAR_MODE, + MMCBR_IVAR_OCR, + MMCBR_IVAR_POWER_MODE, + MMCBR_IVAR_VDD, + MMCBR_IVAR_CAPS, + MMCBR_IVAR_TIMING, + MMCBR_IVAR_MAX_DATA, +// MMCBR_IVAR_, +}; + +/* + * Simplified accessors for pci devices + */ +#define MMCBR_ACCESSOR(var, ivar, type) \ + __BUS_ACCESSOR(mmcbr, var, MMCBR, ivar, type) + +MMCBR_ACCESSOR(bus_mode, BUS_MODE, int) +MMCBR_ACCESSOR(bus_width, BUS_WIDTH, int) +MMCBR_ACCESSOR(chip_select, CHIP_SELECT, int) +MMCBR_ACCESSOR(clock, CLOCK, int) +MMCBR_ACCESSOR(f_max, F_MAX, int) +MMCBR_ACCESSOR(f_min, F_MIN, int) +MMCBR_ACCESSOR(host_ocr, HOST_OCR, int) +MMCBR_ACCESSOR(mode, MODE, int) +MMCBR_ACCESSOR(ocr, OCR, int) +MMCBR_ACCESSOR(power_mode, POWER_MODE, int) +MMCBR_ACCESSOR(vdd, VDD, int) +MMCBR_ACCESSOR(caps, CAPS, int) +MMCBR_ACCESSOR(timing, TIMING, int) +MMCBR_ACCESSOR(max_data, MAX_DATA, int) + +static int __inline +mmcbr_update_ios(device_t dev) +{ + return (MMCBR_UPDATE_IOS(device_get_parent(dev), dev)); +} + +static int __inline +mmcbr_get_ro(device_t dev) +{ + return (MMCBR_GET_RO(device_get_parent(dev), dev)); +} + +#endif /* BUS_MMC_MMCBRVAR_H */ diff --git a/sys/bus/mmc/mmcbus_if.m b/sys/bus/mmc/mmcbus_if.m new file mode 100644 index 0000000000..61a8f8a891 --- /dev/null +++ b/sys/bus/mmc/mmcbus_if.m @@ -0,0 +1,91 @@ +#- +# Copyright (c) 2006 M. Warner Losh +# 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. +# +# Portions of this software may have been developed with reference to +# the SD Simplified Specification. The following disclaimer may apply: +# +# The following conditions apply to the release of the simplified +# specification ("Simplified Specification") by the SD Card Association and +# the SD Group. The Simplified Specification is a subset of the complete SD +# Specification which is owned by the SD Card Association and the SD +# Group. This Simplified Specification is provided on a non-confidential +# basis subject to the disclaimers below. Any implementation of the +# Simplified Specification may require a license from the SD Card +# Association, SD Group, SD-3C LLC or other third parties. +# +# Disclaimers: +# +# The information contained in the Simplified Specification is presented only +# as a standard specification for SD Cards and SD Host/Ancillary products and +# is provided "AS-IS" without any representations or warranties of any +# kind. No responsibility is assumed by the SD Group, SD-3C LLC or the SD +# Card Association for any damages, any infringements of patents or other +# right of the SD Group, SD-3C LLC, the SD Card Association or any third +# parties, which may result from its use. No license is granted by +# implication, estoppel or otherwise under any patent or other rights of the +# SD Group, SD-3C LLC, the SD Card Association or any third party. Nothing +# herein shall be construed as an obligation by the SD Group, the SD-3C LLC +# or the SD Card Association to disclose or distribute any technical +# information, know-how or other confidential information to any third party. +# +# $FreeBSD: src/sys/dev/mmc/mmcbus_if.m,v 1.2 2007/05/26 05:23:36 imp Exp $ +# + +#include +#include +#include + +# +# This is the set of callbacks that mmc bridges call into the bus, or +# that mmc/sd card drivers call to make requests. +# + +INTERFACE mmcbus; + +# +# Queue and wait for a request. +# +METHOD int wait_for_request { + device_t brdev; + device_t reqdev; + struct mmc_request *req; +}; + +# +# Claim the current bridge, blocking the current thread until the host +# is no longer busy. +# +METHOD int acquire_bus { + device_t brdev; + device_t reqdev; +} + +# +# Release the current bridge. +# +METHOD int release_bus { + device_t brdev; + device_t reqdev; +} diff --git a/sys/bus/mmc/mmcreg.h b/sys/bus/mmc/mmcreg.h new file mode 100644 index 0000000000..548af5895a --- /dev/null +++ b/sys/bus/mmc/mmcreg.h @@ -0,0 +1,440 @@ +/*- + * Copyright (c) 2006 M. Warner Losh. 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 ``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 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. + * + * Portions of this software may have been developed with reference to + * the SD Simplified Specification. The following disclaimer may apply: + * + * The following conditions apply to the release of the simplified + * specification ("Simplified Specification") by the SD Card Association and + * the SD Group. The Simplified Specification is a subset of the complete SD + * Specification which is owned by the SD Card Association and the SD + * Group. This Simplified Specification is provided on a non-confidential + * basis subject to the disclaimers below. Any implementation of the + * Simplified Specification may require a license from the SD Card + * Association, SD Group, SD-3C LLC or other third parties. + * + * Disclaimers: + * + * The information contained in the Simplified Specification is presented only + * as a standard specification for SD Cards and SD Host/Ancillary products and + * is provided "AS-IS" without any representations or warranties of any + * kind. No responsibility is assumed by the SD Group, SD-3C LLC or the SD + * Card Association for any damages, any infringements of patents or other + * right of the SD Group, SD-3C LLC, the SD Card Association or any third + * parties, which may result from its use. No license is granted by + * implication, estoppel or otherwise under any patent or other rights of the + * SD Group, SD-3C LLC, the SD Card Association or any third party. Nothing + * herein shall be construed as an obligation by the SD Group, the SD-3C LLC + * or the SD Card Association to disclose or distribute any technical + * information, know-how or other confidential information to any third party. + * + * $FreeBSD: src/sys/dev/mmc/mmcreg.h,v 1.8 2009/02/03 04:28:45 imp Exp $ + */ + +#ifndef BUS_MMC_MMCREG_H +#define BUS_MMC_MMCREG_H + +/* + * This file contains the register definitions for the mmc and sd busses. + * They are taken from publicly available sources. + */ + +struct mmc_data; +struct mmc_request; + +struct mmc_command { + uint32_t opcode; + uint32_t arg; + uint32_t resp[4]; + uint32_t flags; /* Expected responses */ +#define MMC_RSP_PRESENT (1ul << 0) /* Response */ +#define MMC_RSP_136 (1ul << 1) /* 136 bit response */ +#define MMC_RSP_CRC (1ul << 2) /* Expect valid crc */ +#define MMC_RSP_BUSY (1ul << 3) /* Card may send busy */ +#define MMC_RSP_OPCODE (1ul << 4) /* Response include opcode */ +#define MMC_RSP_MASK 0x1ful +#define MMC_CMD_AC (0ul << 5) /* Addressed Command, no data */ +#define MMC_CMD_ADTC (1ul << 5) /* Addressed Data transfer cmd */ +#define MMC_CMD_BC (2ul << 5) /* Broadcast command, no response */ +#define MMC_CMD_BCR (3ul << 5) /* Broadcast command with response */ +#define MMC_CMD_MASK (3ul << 5) + +/* Possible response types defined in the standard: */ +#define MMC_RSP_NONE (0) +#define MMC_RSP_R1 (MMC_RSP_PRESENT | MMC_RSP_CRC | MMC_RSP_OPCODE) +#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_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) + uint32_t retries; + uint32_t error; +#define MMC_ERR_NONE 0 +#define MMC_ERR_TIMEOUT 1 +#define MMC_ERR_BADCRC 2 +#define MMC_ERR_FIFO 3 +#define MMC_ERR_FAILED 4 +#define MMC_ERR_INVALID 5 +#define MMC_ERR_NO_MEMORY 6 +#define MMC_ERR_MAX 6 + struct mmc_data *data; /* Data segment with cmd */ + struct mmc_request *mrq; /* backpointer to request */ +}; + +/* + * R1 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 R1_OUT_OF_RANGE (1u << 31) /* erx, c */ +#define R1_ADDRESS_ERROR (1u << 30) /* erx, c */ +#define R1_BLOCK_LEN_ERROR (1u << 29) /* erx, c */ +#define R1_ERASE_SEQ_ERROR (1u << 28) /* er, c */ +#define R1_ERASE_PARAM (1u << 27) /* erx, c */ +#define R1_WP_VIOLATION (1u << 26) /* erx, c */ +#define R1_CARD_IS_LOCKED (1u << 25) /* sx, a */ +#define R1_LOCK_UNLOCK_FAILED (1u << 24) /* erx, c */ +#define R1_COM_CRC_ERROR (1u << 23) /* er, b */ +#define R1_ILLEGAL_COMMAND (1u << 22) /* er, b */ +#define R1_CARD_ECC_FAILED (1u << 21) /* erx, c */ +#define R1_CC_ERROR (1u << 20) /* erx, c */ +#define R1_ERROR (1u << 19) /* erx, c */ +#define R1_CSD_OVERWRITE (1u << 16) /* erx, c */ +#define R1_WP_ERASE_SKIP (1u << 15) /* erx, c */ +#define R1_CARD_ECC_DISABLED (1u << 14) /* sx, a */ +#define R1_ERASE_RESET (1u << 13) /* sr, c */ +#define R1_CURRENT_STATE_MASK (0xfu << 9) /* sx, b */ +#define R1_READY_FOR_DATA (1u << 8) /* sx, a */ +#define R1_APP_CMD (1u << 5) /* sr, c */ +#define R1_AKE_SEQ_ERROR (1u << 3) /* er, c */ +#define R1_STATUS(x) (x & 0xFFFFE000 +#define R1_CURRENT_STATE(x) ((x) & R1_CURRENT_STATE_MASK) >> 9 +#define R1_STATE_IDLE 0 +#define R1_STATE_READY 1 +#define R1_STATE_IDENT 2 +#define R1_STATE_STBY 3 +#define R1_STATE_TRAN 4 +#define R1_STATE_DATA 5 +#define R1_STATE_RCV 6 +#define R1_STATE_PRG 7 +#define R1_STATE_DIS 8 + +struct mmc_data { + size_t len; /* size of the data */ + size_t xfer_len; + void *data; /* data buffer */ + uint32_t flags; +#define MMC_DATA_WRITE (1UL << 0) +#define MMC_DATA_READ (1UL << 1) +#define MMC_DATA_STREAM (1UL << 2) +#define MMC_DATA_MULTI (1UL << 3) + struct mmc_request *mrq; +}; + +struct mmc_request { + struct mmc_command *cmd; + struct mmc_command *stop; + void (*done)(struct mmc_request *); /* Completion function */ + void *done_data; /* requestor set data */ + uint32_t flags; +#define MMC_REQ_DONE 1 +}; + +/* Command definitions */ + +/* Class 0 and 1: Basic commands & read stream commands */ +#define MMC_GO_IDLE_STATE 0 +#define MMC_SEND_OP_COND 1 +#define MMC_ALL_SEND_CID 2 +#define MMC_SET_RELATIVE_ADDR 3 +#define SD_SEND_RELATIVE_ADDR 3 +#define MMC_SET_DSR 4 + /* reserved: 5 */ +#define MMC_SWITCH_FUNC 6 +#define MMC_SWITCH_FUNC_CMDS 0 +#define MMC_SWITCH_FUNC_SET 1 +#define MMC_SWITCH_FUNC_CLR 2 +#define MMC_SWITCH_FUNC_WR 3 +#define MMC_SELECT_CARD 7 +#define MMC_DESELECT_CARD 7 +#define MMC_SEND_EXT_CSD 8 +#define SD_SEND_IF_COND 8 +#define MMC_SEND_CSD 9 +#define MMC_SEND_CID 10 +#define MMC_READ_DAT_UNTIL_STOP 11 +#define MMC_STOP_TRANSMISSION 12 +#define MMC_SEND_STATUS 13 +#define MMC_BUSTEST_R 14 +#define MMC_GO_INACTIVE_STATE 15 +#define MMC_BUSTEST_W 19 + +/* Class 2: Block oriented read commands */ +#define MMC_SET_BLOCKLEN 16 +#define MMC_READ_SINGLE_BLOCK 17 +#define MMC_READ_MULTIPLE_BLOCK 18 + /* reserved: 19 */ + +/* Class 3: Stream write commands */ +#define MMC_WRITE_DAT_UNTIL_STOP 20 + /* reserved: 21 */ + /* reserved: 22 */ + +/* Class 4: Block oriented write commands */ +#define MMC_SET_BLOCK_COUNT 23 +#define MMC_WRITE_BLOCK 24 +#define MMC_WRITE_MULTIPLE_BLOCK 25 +#define MMC_PROGARM_CID 26 +#define MMC_PROGRAM_CSD 27 + +/* Class 6: Block oriented write protection commands */ +#define MMC_SET_WRITE_PROT 28 +#define MMC_CLR_WRITE_PROT 29 +#define MMC_SEND_WRITE_PROT 30 + /* reserved: 31 */ + +/* Class 5: Erase commands */ +#define SD_ERASE_WR_BLK_START 32 +#define SD_ERASE_WR_BLK_END 33 + /* 34 -- reserved old command */ +#define MMC_ERASE_GROUP_START 35 +#define MMC_ERASE_GROUP_END 36 + /* 37 -- reserved old command */ +#define MMC_ERASE 38 + +/* Class 9: I/O mode commands */ +#define MMC_FAST_IO 39 +#define MMC_GO_IRQ_STATE 40 + /* reserved: 41 */ + +/* Class 7: Lock card */ +#define MMC_LOCK_UNLOCK 42 + /* reserved: 43 */ + /* reserved: 44 */ + /* reserved: 45 */ + /* reserved: 46 */ + /* reserved: 47 */ + /* reserved: 48 */ + /* reserved: 49 */ + /* reserved: 50 */ + /* reserved: 51 */ + /* reserved: 54 */ + +/* Class 8: Application specific commands */ +#define MMC_APP_CMD 55 +#define MMC_GEN_CMD 56 + /* reserved: 57 */ + /* reserved: 58 */ + /* reserved: 59 */ + /* reserved for mfg: 60 */ + /* reserved for mfg: 61 */ + /* reserved for mfg: 62 */ + /* reserved for mfg: 63 */ + +/* Class 9: I/O cards (sd) */ +#define SD_IO_RW_DIRECT 52 +#define SD_IO_RW_EXTENDED 53 + +/* Class 10: Switch function commands */ +#define SD_SWITCH_FUNC 6 + /* reserved: 34 */ + /* reserved: 35 */ + /* reserved: 36 */ + /* reserved: 37 */ + /* reserved: 50 */ + /* reserved: 57 */ + + +/* Application specific commands for SD */ +#define ACMD_SET_BUS_WIDTH 6 +#define ACMD_SD_STATUS 13 +#define ACMD_SEND_NUM_WR_BLOCKS 22 +#define ACMD_SET_WR_BLK_ERASE_COUNT 23 +#define ACMD_SD_SEND_OP_COND 41 +#define ACMD_SET_CLR_CARD_DETECT 42 +#define ACMD_SEND_SCR 51 + +/* + * EXT_CSD fields + */ +#define EXT_CSD_ERASE_GRP_DEF 175 /* R/W */ +#define EXT_CSD_BUS_WIDTH 183 /* R/W */ +#define EXT_CSD_HS_TIMING 185 /* R/W */ +#define EXT_CSD_CARD_TYPE 196 /* RO */ +#define EXT_CSD_REV 192 /* RO */ +#define EXT_CSD_SEC_CNT 212 /* RO, 4 bytes */ +#define EXT_CSD_ERASE_TO_MULT 223 /* RO */ +#define EXT_CSD_ERASE_GRP_SIZE 224 /* RO */ + +/* + * EXT_CSD field definitions + */ +#define EXT_CSD_CMD_SET_NORMAL 1 +#define EXT_CSD_CMD_SET_SECURE 2 +#define EXT_CSD_CMD_SET_CPSECURE 4 + +#define EXT_CSD_CARD_TYPE_26 1 +#define EXT_CSD_CARD_TYPE_52 2 + +#define EXT_CSD_BUS_WIDTH_1 0 +#define EXT_CSD_BUS_WIDTH_4 1 +#define EXT_CSD_BUS_WIDTH_8 2 + +#define MMC_TYPE_26_MAX_HS 26000000 +#define MMC_TYPE_52_MAX_HS 52000000 + +/* + * SD bus widths + */ +#define SD_BUS_WIDTH_1 0 +#define SD_BUS_WIDTH_4 2 + +/* + * SD Switch + */ +#define SD_SWITCH_MODE_CHECK 0 +#define SD_SWITCH_MODE_SET 1 +#define SD_SWITCH_GROUP1 0 +#define SD_SWITCH_NORMAL_MODE 0 +#define SD_SWITCH_HS_MODE 1 +#define SD_SWITCH_NOCHANGE 0xF + +#define SD_MAX_HS 50000000 + +/* OCR bits */ + +/* + * in SD 2.0 spec, bits 8-14 are now marked reserved + * Low voltage in SD2.0 spec is bit 7, TBD voltage + * Low voltage in MC 3.31 spec is bit 7, 1.65-1.95V + * Specs prior to MMC 3.31 defined bits 0-7 as voltages down to 1.5V. + * 3.31 redefined them to be reserved and also said that cards had to + * support the 2.7-3.6V and fixed the OCR to be 0xfff8000 for high voltage + * cards. MMC 4.0 says that a dual voltage card responds with 0xfff8080. + * Looks like the fine-grained control of the voltage tolerance ranges + * was abandoned. + * + * The MMC_OCR_CCS appears to be valid for only SD cards. + */ +#define MMC_OCR_VOLTAGE 0x3fffffffU /* Vdd Voltage mask */ +#define MMC_OCR_LOW_VOLTAGE (1u << 7) /* Low Voltage Range -- tbd */ +#define MMC_OCR_200_210 (1U << 8) /* Vdd voltage 2.00 ~ 2.10 */ +#define MMC_OCR_210_220 (1U << 9) /* Vdd voltage 2.10 ~ 2.20 */ +#define MMC_OCR_220_230 (1U << 10) /* Vdd voltage 2.20 ~ 2.30 */ +#define MMC_OCR_230_240 (1U << 11) /* Vdd voltage 2.30 ~ 2.40 */ +#define MMC_OCR_240_250 (1U << 12) /* Vdd voltage 2.40 ~ 2.50 */ +#define MMC_OCR_250_260 (1U << 13) /* Vdd voltage 2.50 ~ 2.60 */ +#define MMC_OCR_260_270 (1U << 14) /* Vdd voltage 2.60 ~ 2.70 */ +#define MMC_OCR_270_280 (1U << 15) /* Vdd voltage 2.70 ~ 2.80 */ +#define MMC_OCR_280_290 (1U << 16) /* Vdd voltage 2.80 ~ 2.90 */ +#define MMC_OCR_290_300 (1U << 17) /* Vdd voltage 2.90 ~ 3.00 */ +#define MMC_OCR_300_310 (1U << 18) /* Vdd voltage 3.00 ~ 3.10 */ +#define MMC_OCR_310_320 (1U << 19) /* Vdd voltage 3.10 ~ 3.20 */ +#define MMC_OCR_320_330 (1U << 20) /* Vdd voltage 3.20 ~ 3.30 */ +#define MMC_OCR_330_340 (1U << 21) /* Vdd voltage 3.30 ~ 3.40 */ +#define MMC_OCR_340_350 (1U << 22) /* Vdd voltage 3.40 ~ 3.50 */ +#define MMC_OCR_350_360 (1U << 23) /* Vdd voltage 3.50 ~ 3.60 */ +#define MMC_OCR_CCS (1u << 30) /* Card Capacity status (SD vs SDHC) */ +#define MMC_OCR_CARD_BUSY (1U << 31) /* Card Power up status */ + +/* CSD -- decoded structure */ +struct mmc_cid { + uint32_t mid; + char pnm[8]; + uint32_t psn; + uint16_t oid; + uint16_t mdt_year; + uint8_t mdt_month; + uint8_t prv; + uint8_t fwrev; +}; + +struct mmc_csd +{ + uint8_t csd_structure; + uint8_t spec_vers; + uint16_t ccc; + uint16_t tacc; + uint32_t nsac; + uint32_t r2w_factor; + uint32_t tran_speed; + uint32_t read_bl_len; + uint32_t write_bl_len; + uint32_t vdd_r_curr_min; + uint32_t vdd_r_curr_max; + uint32_t vdd_w_curr_min; + uint32_t vdd_w_curr_max; + uint32_t wp_grp_size; + uint32_t erase_sector; + uint64_t capacity; + unsigned int read_bl_partial:1, + read_blk_misalign:1, + write_bl_partial:1, + write_blk_misalign:1, + dsr_imp:1, + erase_blk_en:1, + wp_grp_enable:1; +}; + +struct mmc_scr +{ + unsigned char sda_vsn; + unsigned char bus_widths; +#define SD_SCR_BUS_WIDTH_1 (1<<0) +#define SD_SCR_BUS_WIDTH_4 (1<<2) +}; + +struct mmc_sd_status +{ + uint8_t bus_width; + uint8_t secured_mode; + uint16_t card_type; + uint16_t prot_area; + uint8_t speed_class; + uint8_t perf_move; + uint8_t au_size; + uint16_t erase_size; + uint8_t erase_timeout; + uint8_t erase_offset; +}; + +/* + * Older versions of the MMC standard had a variable sector size. However, + * I've been able to find no old MMC or SD cards that have a non 512 + * byte sector size anywhere, so we assume that such cards are very rare + * and only note their existance in passing here... + */ +#define MMC_SECTOR_SIZE 512 + +#endif /* BUS_MMCREG_H */ diff --git a/sys/bus/mmc/mmcvar.h b/sys/bus/mmc/mmcvar.h new file mode 100644 index 0000000000..cd8aeb3028 --- /dev/null +++ b/sys/bus/mmc/mmcvar.h @@ -0,0 +1,93 @@ +/*- + * Copyright (c) 2006 Bernd Walter. All rights reserved. + * Copyright (c) 2006 M. Warner Losh. 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 ``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 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. + * + * Portions of this software may have been developed with reference to + * the SD Simplified Specification. The following disclaimer may apply: + * + * The following conditions apply to the release of the simplified + * specification ("Simplified Specification") by the SD Card Association and + * the SD Group. The Simplified Specification is a subset of the complete SD + * Specification which is owned by the SD Card Association and the SD + * Group. This Simplified Specification is provided on a non-confidential + * basis subject to the disclaimers below. Any implementation of the + * Simplified Specification may require a license from the SD Card + * Association, SD Group, SD-3C LLC or other third parties. + * + * Disclaimers: + * + * The information contained in the Simplified Specification is presented only + * as a standard specification for SD Cards and SD Host/Ancillary products and + * is provided "AS-IS" without any representations or warranties of any + * kind. No responsibility is assumed by the SD Group, SD-3C LLC or the SD + * Card Association for any damages, any infringements of patents or other + * right of the SD Group, SD-3C LLC, the SD Card Association or any third + * parties, which may result from its use. No license is granted by + * implication, estoppel or otherwise under any patent or other rights of the + * SD Group, SD-3C LLC, the SD Card Association or any third party. Nothing + * herein shall be construed as an obligation by the SD Group, the SD-3C LLC + * or the SD Card Association to disclose or distribute any technical + * information, know-how or other confidential information to any third party. + * + * "$FreeBSD: src/sys/dev/mmc/mmcvar.h,v 1.9 2008/10/29 20:01:26 mav Exp $" + */ + +#ifndef BUS_MMC_MMCVAR_H +#define BUS_MMC_MMCVAR_H + +#include + +enum mmc_device_ivars { + MMC_IVAR_DSR_IMP, + MMC_IVAR_MEDIA_SIZE, + MMC_IVAR_RCA, + MMC_IVAR_SECTOR_SIZE, + MMC_IVAR_TRAN_SPEED, + MMC_IVAR_READ_ONLY, + MMC_IVAR_HIGH_CAP, + MMC_IVAR_CARD_TYPE, + MMC_IVAR_BUS_WIDTH, + MMC_IVAR_ERASE_SECTOR, + MMC_IVAR_MAX_DATA, +// MMC_IVAR_, +}; + +/* + * Simplified accessors for pci devices + */ +#define MMC_ACCESSOR(var, ivar, type) \ + __BUS_ACCESSOR(mmc, var, MMC, ivar, type) + +MMC_ACCESSOR(dsr_imp, DSR_IMP, int) +MMC_ACCESSOR(media_size, MEDIA_SIZE, off_t) +MMC_ACCESSOR(rca, RCA, int) +MMC_ACCESSOR(sector_size, SECTOR_SIZE, int) +MMC_ACCESSOR(tran_speed, TRAN_SPEED, int) +MMC_ACCESSOR(read_only, READ_ONLY, int) +MMC_ACCESSOR(high_cap, HIGH_CAP, int) +MMC_ACCESSOR(card_type, CARD_TYPE, int) +MMC_ACCESSOR(bus_width, BUS_WIDTH, int) +MMC_ACCESSOR(erase_sector, ERASE_SECTOR, int) +MMC_ACCESSOR(max_data, MAX_DATA, int) + +#endif /* BUS_MMC_MMCVAR_H */ diff --git a/sys/conf/files b/sys/conf/files index afdff4b403..9c721ff3da 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -442,6 +442,10 @@ dev/raid/mlx/mlx_disk.c optional mlx dev/raid/mlx/mlx_pci.c optional mlx dev/raid/mlx/mlx.c optional mlx dev/raid/mly/mly.c optional mly +bus/mmc/mmc.c optional mmc +bus/mmc/mmcbr_if.m standard +bus/mmc/mmcbus_if.m standard +dev/disk/mmcsd/mmcsd.c optional mmcsd dev/disk/mpt/mpt.c optional mpt dev/disk/mpt/mpt_cam.c optional mpt dev/disk/mpt/mpt_debug.c optional mpt @@ -478,6 +482,7 @@ dev/serial/rp/rp_isa.c optional nrp isa dev/serial/rp/rp_pci.c optional nrp pci dev/netif/sbni/if_sbni_pci.c optional sbni pci dev/netif/sbsh/if_sbsh.c optional sbsh +dev/disk/sdhci/sdhci.c optional sdhci pci bus/smbus/smbus_if.m optional smbus bus/smbus/smbconf.c optional smbus bus/smbus/smbus.c optional smbus diff --git a/sys/conf/kmod.mk b/sys/conf/kmod.mk index 3467321477..a600fbfd42 100644 --- a/sys/conf/kmod.mk +++ b/sys/conf/kmod.mk @@ -1,6 +1,5 @@ # From: @(#)bsd.prog.mk 5.26 (Berkeley) 6/25/91 # $FreeBSD: src/sys/conf/kmod.mk,v 1.82.2.15 2003/02/10 13:11:50 nyan Exp $ -# $DragonFly: src/sys/conf/kmod.mk,v 1.35 2008/08/27 16:35:19 hasso Exp $ # # The include file handles installing Kernel Loadable Device # drivers (KLD's). @@ -300,7 +299,8 @@ MFILES?= kern/bus_if.m kern/device_if.m bus/iicbus/iicbb_if.m \ dev/acpica5/acpi_if.m dev/disk/nata/ata_if.m \ dev/sound/pcm/ac97_if.m dev/sound/pcm/channel_if.m \ dev/sound/pcm/feeder_if.m dev/sound/pcm/mixer_if.m \ - libiconv/iconv_converter_if.m dev/agp/agp_if.m opencrypto/cryptodev_if.m + libiconv/iconv_converter_if.m dev/agp/agp_if.m opencrypto/cryptodev_if.m \ + bus/mmc/mmcbus_if.m bus/mmc/mmcbr_if.m .for _srcsrc in ${MFILES} .for _ext in c h diff --git a/sys/config/LINT b/sys/config/LINT index 6b5f42356c..1ebeb0b0ff 100644 --- a/sys/config/LINT +++ b/sys/config/LINT @@ -2032,6 +2032,17 @@ device cbb options POWERFAIL_NMI # make it beep instead of panicing +# +# MMC/SD +# +# mmc MMC/SD bus +# mmcsd MMC/SD memory card +# sdhci Generic PCI SD Host Controller +# +device mmc +device mmcsd +device sdhci + # # SMB bus # diff --git a/sys/dev/disk/Makefile b/sys/dev/disk/Makefile index 92d7fdc9cb..d1054d52ab 100644 --- a/sys/dev/disk/Makefile +++ b/sys/dev/disk/Makefile @@ -1,10 +1,7 @@ -# $DragonFly: src/sys/dev/disk/Makefile,v 1.4 2008/01/05 08:16:04 pavalos Exp $ -# - .include "${.CURDIR}/../../platform/${MACHINE_PLATFORM}/Makefile.inc" -SUBDIR= aha ahci aic aic7xxx ccd iscsi ispfw md mpt ncv nsp sbp -SUBDIR+= sili stg trm vn vpo +SUBDIR= aha ahci aic aic7xxx ccd iscsi ispfw md mmcsd mpt ncv nsp +SUBDIR+= sbp sdhci sili stg trm vn vpo .for dir in ${SUBDIR} .if empty(DEV_SUPPORT:Mdisk) && \ diff --git a/sys/dev/disk/mmcsd/Makefile b/sys/dev/disk/mmcsd/Makefile new file mode 100644 index 0000000000..f44fe70386 --- /dev/null +++ b/sys/dev/disk/mmcsd/Makefile @@ -0,0 +1,8 @@ +# $FreeBSD: src/sys/modules/mmcsd/Makefile,v 1.1 2006/10/20 06:52:59 imp Exp $ + +.PATH: ${.CURDIR}/../../dev/mmc + +KMOD= mmcsd +SRCS= mmcsd.c mmcbus_if.h device_if.h bus_if.h + +.include diff --git a/sys/dev/disk/mmcsd/mmcsd.c b/sys/dev/disk/mmcsd/mmcsd.c new file mode 100644 index 0000000000..d918312b47 --- /dev/null +++ b/sys/dev/disk/mmcsd/mmcsd.c @@ -0,0 +1,571 @@ +/*- + * Copyright (c) 2006 Bernd Walter. All rights reserved. + * Copyright (c) 2006 M. Warner Losh. 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 ``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 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. + * + * Portions of this software may have been developed with reference to + * the SD Simplified Specification. The following disclaimer may apply: + * + * The following conditions apply to the release of the simplified + * specification ("Simplified Specification") by the SD Card Association and + * the SD Group. The Simplified Specification is a subset of the complete SD + * Specification which is owned by the SD Card Association and the SD + * Group. This Simplified Specification is provided on a non-confidential + * basis subject to the disclaimers below. Any implementation of the + * Simplified Specification may require a license from the SD Card + * Association, SD Group, SD-3C LLC or other third parties. + * + * Disclaimers: + * + * The information contained in the Simplified Specification is presented only + * as a standard specification for SD Cards and SD Host/Ancillary products and + * is provided "AS-IS" without any representations or warranties of any + * kind. No responsibility is assumed by the SD Group, SD-3C LLC or the SD + * Card Association for any damages, any infringements of patents or other + * right of the SD Group, SD-3C LLC, the SD Card Association or any third + * parties, which may result from its use. No license is granted by + * implication, estoppel or otherwise under any patent or other rights of the + * SD Group, SD-3C LLC, the SD Card Association or any third party. Nothing + * herein shall be construed as an obligation by the SD Group, the SD-3C LLC + * or the SD Card Association to disclose or distribute any technical + * information, know-how or other confidential information to any third party. + * + * $FreeBSD: src/sys/dev/mmc/mmcsd.c,v 1.20 2009/02/17 19:17:25 mav Exp $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "mmcbus_if.h" + +struct mmcsd_softc { + device_t dev; + cdev_t dev_t; + struct lock sc_lock; + struct disk disk; + int unit; + struct thread *td; + struct bio_queue_head bio_queue; + daddr_t eblock, eend; /* Range remaining after the last erase. */ + int running; + int suspend; +}; + +/* bus entry points */ +static int mmcsd_probe(device_t dev); +static int mmcsd_attach(device_t dev); +static int mmcsd_detach(device_t dev); + +/* disk routines */ +static d_open_t mmcsd_open; +static d_close_t mmcsd_close; +static d_strategy_t mmcsd_strategy; +static d_dump_t mmcsd_dump; + +static void mmcsd_task(void *arg); + +static const char *mmcsd_card_name(device_t dev); +static int mmcsd_bus_bit_width(device_t dev); + +#define MMCSD_LOCK(_sc) lockmgr(&(_sc)->sc_lock, LK_EXCLUSIVE) +#define MMCSD_UNLOCK(_sc) lockmgr(&(_sc)->sc_lock, LK_RELEASE) +#define MMCSD_LOCK_INIT(_sc) lockinit(&(_sc)->sc_lock, "mmcsd", 0, LK_CANRECURSE) +#define MMCSD_LOCK_DESTROY(_sc) lockuninit(&(_sc)->sc_lock); +#define MMCSD_ASSERT_LOCKED(_sc) KKASSERT(lockstatus(&(_sc)->sc_lock, curthread) != 0); +#define MMCSD_ASSERT_UNLOCKED(_sc) KKASSERT(lockstatus(&(_sc)->sc_lock, curthread) == 0); + +static struct dev_ops mmcsd_ops = { + { "mmcsd", 0, D_DISK }, + .d_open = mmcsd_open, + .d_close = mmcsd_close, + .d_strategy = mmcsd_strategy, + .d_dump = mmcsd_dump, +}; + +static int +mmcsd_probe(device_t dev) +{ + + device_quiet(dev); + device_set_desc(dev, "MMC/SD Memory Card"); + return (0); +} + +static int +mmcsd_attach(device_t dev) +{ + struct mmcsd_softc *sc; + struct disk_info info; + cdev_t dsk; + intmax_t mb; + char unit; + + sc = device_get_softc(dev); + sc->dev = dev; + MMCSD_LOCK_INIT(sc); + + dsk = disk_create(sc->unit, &sc->disk, &mmcsd_ops); + dsk->si_drv1 = sc; + sc->dev_t = dsk; + + dsk->si_iosize_max = 4*1024*1024; /* Maximum defined SD card AU size. */ + + bzero(&info, sizeof(info)); + info.d_media_blksize = mmc_get_sector_size(dev); + info.d_media_blocks = mmc_get_media_size(dev); + disk_setdiskinfo(&sc->disk, &info); + + /* + * Display in most natural units. There's no cards < 1MB. + * The SD standard goes to 2GiB, but the data format supports + * up to 4GiB and some card makers push it up to this limit. + * The SDHC standard only goes to 32GiB (the data format in + * SDHC is good to 2TiB however, which isn't too ugly at + * 2048GiBm, so we note it in passing here and don't add the + * code to print TiB). + */ + mb = (info.d_media_blksize * info.d_media_blocks) >> 20; /* 1MiB == 1 << 20 */ + unit = 'M'; + if (mb >= 10240) { /* 1GiB = 1024 MiB */ + unit = 'G'; + mb /= 1024; + } + device_printf(dev, "%ju%cB <%s Memory Card>%s at %s %dMHz/%dbit\n", + mb, unit, mmcsd_card_name(dev), + mmc_get_read_only(dev) ? " (read-only)" : "", + device_get_nameunit(device_get_parent(dev)), + mmc_get_tran_speed(dev) / 1000000, mmcsd_bus_bit_width(dev)); + + bioq_init(&sc->bio_queue); + + sc->running = 1; + sc->suspend = 0; + sc->eblock = sc->eend = 0; + kthread_create(&mmcsd_task, sc, &sc->td, "mmc/sd card task"); + + return (0); +} + +static int +mmcsd_detach(device_t dev) +{ + struct mmcsd_softc *sc = device_get_softc(dev); + struct buf *q_bp; + struct bio *q_bio; + + MMCSD_LOCK(sc); + sc->suspend = 0; + if (sc->running > 0) { + /* kill thread */ + sc->running = 0; + wakeup(sc); + /* wait for thread to finish. */ + while (sc->running != -1) + lksleep(sc, &sc->sc_lock, 0, "detach", 0); + } + MMCSD_UNLOCK(sc); + + /* Flush the request queue. + * + * XXX: Return all queued I/O with ENXIO. Is this correct? + */ + while ((q_bio = bioq_first(&sc->bio_queue)) != NULL){ + bioq_remove(&sc->bio_queue, q_bio); + q_bp = q_bio->bio_buf; + q_bp->b_resid = q_bp->b_bcount; + q_bp->b_error = ENXIO; + q_bp->b_flags |= B_ERROR; + biodone(q_bio); + } + + /* kill disk */ + disk_destroy(&sc->disk); + + MMCSD_LOCK_DESTROY(sc); + + return (0); +} + +static int +mmcsd_suspend(device_t dev) +{ + struct mmcsd_softc *sc = device_get_softc(dev); + + MMCSD_LOCK(sc); + sc->suspend = 1; + if (sc->running > 0) { + /* kill thread */ + sc->running = 0; + wakeup(sc); + /* wait for thread to finish. */ + while (sc->running != -1) + lksleep(sc, &sc->sc_lock, 0, "detach", 0); + } + MMCSD_UNLOCK(sc); + return (0); +} + +static int +mmcsd_resume(device_t dev) +{ + struct mmcsd_softc *sc = device_get_softc(dev); + + MMCSD_LOCK(sc); + sc->suspend = 0; + if (sc->running <= 0) { + sc->running = 1; + MMCSD_UNLOCK(sc); + kthread_create(&mmcsd_task, sc, &sc->td, "mmc/sd card task"); + } else + MMCSD_UNLOCK(sc); + return (0); +} + +static int +mmcsd_open(struct dev_open_args *ap) +{ + return (0); +} + +static int +mmcsd_close(struct dev_close_args *ap) +{ + return (0); +} + +static int +mmcsd_strategy(struct dev_strategy_args *ap) +{ + struct mmcsd_softc *sc; + struct bio *bio = ap->a_bio; + + sc = (struct mmcsd_softc *)ap->a_head.a_dev->si_drv1; + MMCSD_LOCK(sc); + if (sc->running > 0 || sc->suspend > 0) { + bioqdisksort(&sc->bio_queue, bio); + MMCSD_UNLOCK(sc); + wakeup(sc); + } else { + MMCSD_UNLOCK(sc); + /* XXX */ + bio->bio_buf->b_error = ENXIO; + bio->bio_buf->b_flags |= B_ERROR; + bio->bio_buf->b_resid = bio->bio_buf->b_bcount; + biodone(bio); + } + return (0); +} + +static daddr_t +mmcsd_rw(struct mmcsd_softc *sc, struct bio *bio) +{ + daddr_t block, end; + struct mmc_command cmd; + struct mmc_command stop; + struct mmc_request req; + struct mmc_data data; + device_t dev = sc->dev; + int sz = sc->disk.d_info.d_media_blksize; + struct buf *bp = bio->bio_buf; + + block = bio->bio_offset / sz; + end = block + (bp->b_bcount / sz); + while (block < end) { + char *vaddr = bp->b_data + + (block - (bio->bio_offset / sz)) * sz; + int numblocks = min(end - block, mmc_get_max_data(dev)); + memset(&req, 0, sizeof(req)); + memset(&cmd, 0, sizeof(cmd)); + memset(&stop, 0, sizeof(stop)); + req.cmd = &cmd; + cmd.data = &data; + if (bp->b_cmd == BUF_CMD_READ) { + if (numblocks > 1) + cmd.opcode = MMC_READ_MULTIPLE_BLOCK; + else + cmd.opcode = MMC_READ_SINGLE_BLOCK; + } else { + if (numblocks > 1) + cmd.opcode = MMC_WRITE_MULTIPLE_BLOCK; + else + cmd.opcode = MMC_WRITE_BLOCK; + } + cmd.arg = block; + if (!mmc_get_high_cap(dev)) + cmd.arg <<= 9; + cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + data.data = vaddr; + data.mrq = &req; + if (bp->b_cmd == BUF_CMD_READ) + data.flags = MMC_DATA_READ; + else + data.flags = MMC_DATA_WRITE; + data.len = numblocks * sz; + if (numblocks > 1) { + data.flags |= MMC_DATA_MULTI; + stop.opcode = MMC_STOP_TRANSMISSION; + stop.arg = 0; + stop.flags = MMC_RSP_R1B | MMC_CMD_AC; + req.stop = &stop; + } +// kprintf("Len %d %lld-%lld flags %#x sz %d\n", +// (int)data.len, (long long)block, (long long)end, data.flags, sz); + MMCBUS_WAIT_FOR_REQUEST(device_get_parent(dev), dev, + &req); + if (req.cmd->error != MMC_ERR_NONE) + break; + block += numblocks; + } + return (block); +} + +static daddr_t +mmcsd_delete(struct mmcsd_softc *sc, struct bio *bio) +{ + daddr_t block, end, start, stop; + struct mmc_command cmd; + struct mmc_request req; + device_t dev = sc->dev; + int sz = sc->disk.d_info.d_media_blksize; + int erase_sector; + struct buf *bp = bio->bio_buf; + + block = bio->bio_offset / sz; + end = block + (bp->b_bcount / sz); + /* Coalesce with part remaining from previous request. */ + if (block > sc->eblock && block <= sc->eend) + block = sc->eblock; + if (end >= sc->eblock && end < sc->eend) + end = sc->eend; + /* Safe round to the erase sector boundaries. */ + erase_sector = mmc_get_erase_sector(dev); + start = block + erase_sector - 1; /* Round up. */ + start -= start % erase_sector; + stop = end; /* Round down. */ + stop -= end % erase_sector; + /* We can't erase area smaller then sector, store it for later. */ + if (start >= stop) { + sc->eblock = block; + sc->eend = end; + return (end); + } + + /* Set erase start position. */ + memset(&req, 0, sizeof(req)); + memset(&cmd, 0, sizeof(cmd)); + req.cmd = &cmd; + if (mmc_get_card_type(dev) == mode_sd) + cmd.opcode = SD_ERASE_WR_BLK_START; + else + cmd.opcode = MMC_ERASE_GROUP_START; + cmd.arg = start; + if (!mmc_get_high_cap(dev)) + cmd.arg <<= 9; + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + MMCBUS_WAIT_FOR_REQUEST(device_get_parent(dev), dev, + &req); + if (req.cmd->error != MMC_ERR_NONE) { + kprintf("erase err1: %d\n", req.cmd->error); + return (block); + } + /* Set erase stop position. */ + memset(&req, 0, sizeof(req)); + memset(&cmd, 0, sizeof(cmd)); + req.cmd = &cmd; + if (mmc_get_card_type(dev) == mode_sd) + cmd.opcode = SD_ERASE_WR_BLK_END; + else + cmd.opcode = MMC_ERASE_GROUP_END; + cmd.arg = stop; + if (!mmc_get_high_cap(dev)) + cmd.arg <<= 9; + cmd.arg--; + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + MMCBUS_WAIT_FOR_REQUEST(device_get_parent(dev), dev, + &req); + if (req.cmd->error != MMC_ERR_NONE) { + kprintf("erase err2: %d\n", req.cmd->error); + return (block); + } + /* Erase range. */ + memset(&req, 0, sizeof(req)); + memset(&cmd, 0, sizeof(cmd)); + req.cmd = &cmd; + cmd.opcode = MMC_ERASE; + cmd.arg = 0; + cmd.flags = MMC_RSP_R1B | MMC_CMD_AC; + MMCBUS_WAIT_FOR_REQUEST(device_get_parent(dev), dev, + &req); + if (req.cmd->error != MMC_ERR_NONE) { + kprintf("erase err3 %d\n", req.cmd->error); + return (block); + } + /* Store one of remaining parts for the next call. */ + if ((bio->bio_offset / sz) >= sc->eblock || block == start) { + sc->eblock = stop; /* Predict next forward. */ + sc->eend = end; + } else { + sc->eblock = block; /* Predict next backward. */ + sc->eend = start; + } + return (end); +} + +static int +mmcsd_dump(struct dev_dump_args *ap) +{ +#if 0 + cdev_t cdev = ap->a_head.a_dev; + struct mmcsd_softc *sc = (struct mmcsd_softc *)cdev->si_drv1; + device_t dev = sc->dev; + struct bio bp; + daddr_t block, end; + int length = ap->a_length; + + /* length zero is special and really means flush buffers to media */ + if (!length) + return (0); + + bzero(&bp, sizeof(struct bio)); + bp.bio_driver_info = cdev; + bp.bio_pblkno = offset / sc->disk->d_sectorsize; + bp.bio_bcount = length; + bp.bio_data = virtual; + bp.bio_cmd = BIO_WRITE; + end = bp.bio_pblkno + bp.bio_bcount / sc->disk.d_info.d_media_blksize; + MMCBUS_ACQUIRE_BUS(device_get_parent(dev), dev); + block = mmcsd_rw(sc, &bp); + MMCBUS_RELEASE_BUS(device_get_parent(dev), dev); + return ((end < block) ? EIO : 0); +#endif + return EIO; +} + +static void +mmcsd_task(void *arg) +{ + struct mmcsd_softc *sc = (struct mmcsd_softc*)arg; + struct bio *bp; + int sz; + daddr_t block, end; + device_t dev; + + dev = sc->dev; + while (1) { + MMCSD_LOCK(sc); + do { + if (sc->running == 0) + goto out; + bp = bioq_takefirst(&sc->bio_queue); + if (bp == NULL) + lksleep(sc, &sc->sc_lock, 0, "jobqueue", 0); + } while (bp == NULL); + MMCSD_UNLOCK(sc); + if (bp->bio_buf->b_cmd != BUF_CMD_READ && mmc_get_read_only(dev)) { + bp->bio_buf->b_error = EROFS; + bp->bio_buf->b_resid = bp->bio_buf->b_bcount; + bp->bio_buf->b_flags |= B_ERROR; + biodone(bp); + continue; + } + MMCBUS_ACQUIRE_BUS(device_get_parent(dev), dev); + sz = sc->disk.d_info.d_media_blksize; + block = bp->bio_offset / sz; + end = block + (bp->bio_buf->b_bcount / sz); + if (bp->bio_buf->b_cmd == BUF_CMD_READ || + bp->bio_buf->b_cmd == BUF_CMD_WRITE) { + /* Access to the remaining erase block obsoletes it. */ + if (block < sc->eend && end > sc->eblock) + sc->eblock = sc->eend = 0; + block = mmcsd_rw(sc, bp); + } else if (bp->bio_buf->b_cmd == BUF_CMD_FREEBLKS) { + block = mmcsd_delete(sc, bp); + } + MMCBUS_RELEASE_BUS(device_get_parent(dev), dev); + if (block < end) { + bp->bio_buf->b_error = EIO; + bp->bio_buf->b_resid = (end - block) * sz; + bp->bio_buf->b_flags |= B_ERROR; + } + biodone(bp); + } +out: + /* tell parent we're done */ + sc->running = -1; + MMCSD_UNLOCK(sc); + wakeup(sc); + + kthread_exit(); +} + +static const char * +mmcsd_card_name(device_t dev) +{ + if (mmc_get_card_type(dev) == mode_mmc) + return ("MMC"); + if (mmc_get_high_cap(dev)) + return ("SDHC"); + return ("SD"); +} + +static int +mmcsd_bus_bit_width(device_t dev) +{ + if (mmc_get_bus_width(dev) == bus_width_1) + return (1); + if (mmc_get_bus_width(dev) == bus_width_4) + return (4); + return (8); +} + +static device_method_t mmcsd_methods[] = { + DEVMETHOD(device_probe, mmcsd_probe), + DEVMETHOD(device_attach, mmcsd_attach), + DEVMETHOD(device_detach, mmcsd_detach), + DEVMETHOD(device_suspend, mmcsd_suspend), + DEVMETHOD(device_resume, mmcsd_resume), + {0, 0}, +}; + +static driver_t mmcsd_driver = { + "mmcsd", + mmcsd_methods, + sizeof(struct mmcsd_softc), +}; +static devclass_t mmcsd_devclass; + +DRIVER_MODULE(mmcsd, mmc, mmcsd_driver, mmcsd_devclass, 0, 0); diff --git a/sys/dev/disk/sdhci/Makefile b/sys/dev/disk/sdhci/Makefile new file mode 100644 index 0000000000..256b451694 --- /dev/null +++ b/sys/dev/disk/sdhci/Makefile @@ -0,0 +1,6 @@ +# $FreeBSD: src/sys/modules/sdhci/Makefile,v 1.1 2008/10/21 20:33:40 mav Exp $ + +KMOD= sdhci +SRCS= sdhci.c sdhci.h device_if.h bus_if.h pci_if.h mmcbr_if.h + +.include diff --git a/sys/dev/disk/sdhci/sdhci.c b/sys/dev/disk/sdhci/sdhci.c new file mode 100644 index 0000000000..d81d87fe5a --- /dev/null +++ b/sys/dev/disk/sdhci/sdhci.c @@ -0,0 +1,1572 @@ +/*- + * Copyright (c) 2008 Alexander Motin + * 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 ``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 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. + * + * $FreeBSD: src/sys/dev/sdhci/sdhci.c,v 1.8 2009/02/17 19:12:15 mav Exp $ + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "mmcbr_if.h" +#include "sdhci.h" + +#define DMA_BLOCK_SIZE 4096 +#define DMA_BOUNDARY 0 /* DMA reload every 4K */ + +/* Controller doesn't honor resets unless we touch the clock register */ +#define SDHCI_QUIRK_CLOCK_BEFORE_RESET (1<<0) +/* Controller really supports DMA */ +#define SDHCI_QUIRK_FORCE_DMA (1<<1) +/* Controller has unusable DMA engine */ +#define SDHCI_QUIRK_BROKEN_DMA (1<<2) +/* Controller doesn't like to be reset when there is no card inserted. */ +#define SDHCI_QUIRK_NO_CARD_NO_RESET (1<<3) +/* Controller has flaky internal state so reset it on each ios change */ +#define SDHCI_QUIRK_RESET_ON_IOS (1<<4) +/* Controller can only DMA chunk sizes that are a multiple of 32 bits */ +#define SDHCI_QUIRK_32BIT_DMA_SIZE (1<<5) +/* Controller needs to be reset after each request to stay stable */ +#define SDHCI_QUIRK_RESET_AFTER_REQUEST (1<<6) +/* Controller has an off-by-one issue with timeout value */ +#define SDHCI_QUIRK_INCR_TIMEOUT_CONTROL (1<<7) +/* Controller has broken read timings */ +#define SDHCI_QUIRK_BROKEN_TIMINGS (1<<8) + +static const struct sdhci_device { + uint32_t model; + uint16_t subvendor; + char *desc; + u_int quirks; +} sdhci_devices[] = { + { 0x08221180, 0xffff, "RICOH R5C822 SD", + SDHCI_QUIRK_FORCE_DMA }, + { 0x8034104c, 0xffff, "TI XX21/XX11 SD", + SDHCI_QUIRK_FORCE_DMA }, + { 0x05501524, 0xffff, "ENE CB712 SD", + SDHCI_QUIRK_BROKEN_TIMINGS }, + { 0x05511524, 0xffff, "ENE CB712 SD 2", + SDHCI_QUIRK_BROKEN_TIMINGS }, + { 0x07501524, 0xffff, "ENE CB714 SD", + SDHCI_QUIRK_RESET_ON_IOS | + SDHCI_QUIRK_BROKEN_TIMINGS }, + { 0x07511524, 0xffff, "ENE CB714 SD 2", + SDHCI_QUIRK_RESET_ON_IOS | + SDHCI_QUIRK_BROKEN_TIMINGS }, + { 0x410111ab, 0xffff, "Marvell CaFe SD", + SDHCI_QUIRK_INCR_TIMEOUT_CONTROL }, + { 0x2381197B, 0xffff, "JMicron JMB38X SD", + SDHCI_QUIRK_32BIT_DMA_SIZE | + SDHCI_QUIRK_RESET_AFTER_REQUEST }, + { 0, 0xffff, NULL, + 0 } +}; + +struct sdhci_softc; + +struct sdhci_slot { + struct sdhci_softc *sc; + device_t dev; /* Slot device */ + u_char num; /* Slot number */ + u_char opt; /* Slot options */ +#define SDHCI_HAVE_DMA 1 + uint32_t max_clk; /* Max possible freq */ + uint32_t timeout_clk; /* Timeout freq */ + struct resource *mem_res; /* Memory resource */ + int mem_rid; + bus_dma_tag_t dmatag; + bus_dmamap_t dmamap; + u_char *dmamem; + bus_addr_t paddr; /* DMA buffer address */ + struct task card_task; /* Card presence check task */ + struct callout card_callout; /* Card insert delay callout */ + struct mmc_host host; /* Host parameters */ + struct mmc_request *req; /* Current request */ + struct mmc_command *curcmd; /* Current command of current request */ + + uint32_t intmask; /* Current interrupt mask */ + uint32_t clock; /* Current clock freq. */ + size_t offset; /* Data buffer offset */ + uint8_t hostctrl; /* Current host control register */ + u_char power; /* Current power */ + u_char bus_busy; /* Bus busy status */ + u_char cmd_done; /* CMD command part done flag */ + u_char data_done; /* DAT command part done flag */ + u_char flags; /* Request execution flags */ +#define CMD_STARTED 1 +#define STOP_STARTED 2 +#define SDHCI_USE_DMA 4 /* Use DMA for this req. */ + struct lock lock; /* Slot lock */ +}; + +struct sdhci_softc { + device_t dev; /* Controller device */ + u_int quirks; /* Chip specific quirks */ + struct resource *irq_res; /* IRQ resource */ + int irq_rid; + void *intrhand; /* Interrupt handle */ + + int num_slots; /* Number of slots on this controller */ + struct sdhci_slot slots[6]; +}; + +SYSCTL_NODE(_hw, OID_AUTO, sdhci, CTLFLAG_RD, 0, "sdhci driver"); + +int sdhci_debug; +TUNABLE_INT("hw.sdhci.debug", &sdhci_debug); +SYSCTL_INT(_hw_sdhci, OID_AUTO, debug, CTLFLAG_RW, &sdhci_debug, 0, "Debug level"); + +static inline uint8_t +RD1(struct sdhci_slot *slot, bus_size_t off) +{ + bus_barrier(slot->mem_res, 0, 0xFF, + BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE); + return bus_read_1(slot->mem_res, off); +} + +static inline void +WR1(struct sdhci_slot *slot, bus_size_t off, uint8_t val) +{ + bus_barrier(slot->mem_res, 0, 0xFF, + BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE); + bus_write_1(slot->mem_res, off, val); +} + +static inline uint16_t +RD2(struct sdhci_slot *slot, bus_size_t off) +{ + bus_barrier(slot->mem_res, 0, 0xFF, + BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE); + return bus_read_2(slot->mem_res, off); +} + +static inline void +WR2(struct sdhci_slot *slot, bus_size_t off, uint16_t val) +{ + bus_barrier(slot->mem_res, 0, 0xFF, + BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE); + bus_write_2(slot->mem_res, off, val); +} + +static inline uint32_t +RD4(struct sdhci_slot *slot, bus_size_t off) +{ + bus_barrier(slot->mem_res, 0, 0xFF, + BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE); + return bus_read_4(slot->mem_res, off); +} + +static inline void +WR4(struct sdhci_slot *slot, bus_size_t off, uint32_t val) +{ + bus_barrier(slot->mem_res, 0, 0xFF, + BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE); + bus_write_4(slot->mem_res, off, val); +} + +/* bus entry points */ +static int sdhci_probe(device_t dev); +static int sdhci_attach(device_t dev); +static int sdhci_detach(device_t dev); +static void sdhci_intr(void *); + +static void sdhci_set_clock(struct sdhci_slot *slot, uint32_t clock); +static void sdhci_start(struct sdhci_slot *slot); +static void sdhci_start_data(struct sdhci_slot *slot, struct mmc_data *data); + +static void sdhci_card_task(void *, int); + +/* helper routines */ +#define SDHCI_LOCK(_slot) lockmgr(&(_slot)->lock, LK_EXCLUSIVE) +#define SDHCI_UNLOCK(_slot) lockmgr(&(_slot)->lock, LK_RELEASE) +#define SDHCI_LOCK_INIT(_slot) lockinit(&(_slot)->lock, "sdhci", 0, LK_CANRECURSE) +#define SDHCI_LOCK_DESTROY(_slot) lockuninit(&(_slot)->lock); +#define SDHCI_ASSERT_LOCKED(_slot) KKASSERT(lockstatus(&(_slot)->lock, curthread) != 0); +#define SDHCI_ASSERT_UNLOCKED(_slot) KKASSERT(lockstatus(&(_slot)->lock, curthread) == 0); + +static int +slot_printf(struct sdhci_slot *slot, const char * fmt, ...) +{ + va_list ap; + int retval; + + retval = kprintf("%s-slot%d: ", + device_get_nameunit(slot->sc->dev), slot->num); + + va_start(ap, fmt); + retval += kvprintf(fmt, ap); + va_end(ap); + return (retval); +} + +static void +sdhci_getaddr(void *arg, bus_dma_segment_t *segs, int nsegs, int error) +{ + if (error != 0) { + kprintf("getaddr: error %d\n", error); + return; + } + *(bus_addr_t *)arg = segs[0].ds_addr; +} + +static void +sdhci_dumpregs(struct sdhci_slot *slot) +{ + slot_printf(slot, + "============== REGISTER DUMP ==============\n"); + + slot_printf(slot, "Sys addr: 0x%08x | Version: 0x%08x\n", + RD4(slot, SDHCI_DMA_ADDRESS), RD2(slot, SDHCI_HOST_VERSION)); + slot_printf(slot, "Blk size: 0x%08x | Blk cnt: 0x%08x\n", + RD2(slot, SDHCI_BLOCK_SIZE), RD2(slot, SDHCI_BLOCK_COUNT)); + slot_printf(slot, "Argument: 0x%08x | Trn mode: 0x%08x\n", + RD4(slot, SDHCI_ARGUMENT), RD2(slot, SDHCI_TRANSFER_MODE)); + slot_printf(slot, "Present: 0x%08x | Host ctl: 0x%08x\n", + RD4(slot, SDHCI_PRESENT_STATE), RD1(slot, SDHCI_HOST_CONTROL)); + slot_printf(slot, "Power: 0x%08x | Blk gap: 0x%08x\n", + RD1(slot, SDHCI_POWER_CONTROL), RD1(slot, SDHCI_BLOCK_GAP_CONTROL)); + slot_printf(slot, "Wake-up: 0x%08x | Clock: 0x%08x\n", + RD1(slot, SDHCI_WAKE_UP_CONTROL), RD2(slot, SDHCI_CLOCK_CONTROL)); + slot_printf(slot, "Timeout: 0x%08x | Int stat: 0x%08x\n", + RD1(slot, SDHCI_TIMEOUT_CONTROL), RD4(slot, SDHCI_INT_STATUS)); + slot_printf(slot, "Int enab: 0x%08x | Sig enab: 0x%08x\n", + RD4(slot, SDHCI_INT_ENABLE), RD4(slot, SDHCI_SIGNAL_ENABLE)); + slot_printf(slot, "AC12 err: 0x%08x | Slot int: 0x%08x\n", + RD2(slot, SDHCI_ACMD12_ERR), RD2(slot, SDHCI_SLOT_INT_STATUS)); + slot_printf(slot, "Caps: 0x%08x | Max curr: 0x%08x\n", + RD4(slot, SDHCI_CAPABILITIES), RD4(slot, SDHCI_MAX_CURRENT)); + + slot_printf(slot, + "===========================================\n"); +} + +static void +sdhci_reset(struct sdhci_slot *slot, uint8_t mask) +{ + int timeout; + uint8_t res; + + if (slot->sc->quirks & SDHCI_QUIRK_NO_CARD_NO_RESET) { + if (!(RD4(slot, SDHCI_PRESENT_STATE) & + SDHCI_CARD_PRESENT)) + return; + } + + /* Some controllers need this kick or reset won't work. */ + if ((mask & SDHCI_RESET_ALL) == 0 && + (slot->sc->quirks & SDHCI_QUIRK_CLOCK_BEFORE_RESET)) { + uint32_t clock; + + /* This is to force an update */ + clock = slot->clock; + slot->clock = 0; + sdhci_set_clock(slot, clock); + } + + WR1(slot, SDHCI_SOFTWARE_RESET, mask); + + if (mask & SDHCI_RESET_ALL) { + slot->clock = 0; + slot->power = 0; + } + + /* Wait max 100 ms */ + timeout = 100; + /* Controller clears the bits when it's done */ + while ((res = RD1(slot, SDHCI_SOFTWARE_RESET)) & mask) { + if (timeout == 0) { + slot_printf(slot, + "Reset 0x%x never completed - 0x%x.\n", + (int)mask, (int)res); + sdhci_dumpregs(slot); + return; + } + timeout--; + DELAY(1000); + } +} + +static void +sdhci_init(struct sdhci_slot *slot) +{ + + sdhci_reset(slot, SDHCI_RESET_ALL); + + /* Enable interrupts. */ + slot->intmask = SDHCI_INT_BUS_POWER | SDHCI_INT_DATA_END_BIT | + SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_INDEX | + SDHCI_INT_END_BIT | SDHCI_INT_CRC | SDHCI_INT_TIMEOUT | + SDHCI_INT_CARD_REMOVE | SDHCI_INT_CARD_INSERT | + SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL | + SDHCI_INT_DMA_END | SDHCI_INT_DATA_END | SDHCI_INT_RESPONSE | + SDHCI_INT_ACMD12ERR; + WR4(slot, SDHCI_INT_ENABLE, slot->intmask); + WR4(slot, SDHCI_SIGNAL_ENABLE, slot->intmask); +} + +static void +sdhci_set_clock(struct sdhci_slot *slot, uint32_t clock) +{ + uint32_t res; + uint16_t clk; + int timeout; + + if (clock == slot->clock) + return; + slot->clock = clock; + + /* Turn off the clock. */ + WR2(slot, SDHCI_CLOCK_CONTROL, 0); + /* If no clock requested - left it so. */ + if (clock == 0) + return; + /* Looking for highest freq <= clock. */ + res = slot->max_clk; + for (clk = 1; clk < 256; clk <<= 1) { + if (res <= clock) + break; + res >>= 1; + } + /* Divider 1:1 is 0x00, 2:1 is 0x01, 256:1 is 0x80 ... */ + clk >>= 1; + /* Now we have got divider, set it. */ + clk <<= SDHCI_DIVIDER_SHIFT; + WR2(slot, SDHCI_CLOCK_CONTROL, clk); + /* Enable clock. */ + clk |= SDHCI_CLOCK_INT_EN; + WR2(slot, SDHCI_CLOCK_CONTROL, clk); + /* Wait up to 10 ms until it stabilize. */ + timeout = 10; + while (!((clk = RD2(slot, SDHCI_CLOCK_CONTROL)) + & SDHCI_CLOCK_INT_STABLE)) { + if (timeout == 0) { + slot_printf(slot, + "Internal clock never stabilised.\n"); + sdhci_dumpregs(slot); + return; + } + timeout--; + DELAY(1000); + } + /* Pass clock signal to the bus. */ + clk |= SDHCI_CLOCK_CARD_EN; + WR2(slot, SDHCI_CLOCK_CONTROL, clk); +} + +static void +sdhci_set_power(struct sdhci_slot *slot, u_char power) +{ + uint8_t pwr; + + if (slot->power == power) + return; + slot->power = power; + + /* Turn off the power. */ + pwr = 0; + WR1(slot, SDHCI_POWER_CONTROL, pwr); + /* If power down requested - left it so. */ + if (power == 0) + return; + /* Set voltage. */ + switch (1 << power) { + case MMC_OCR_LOW_VOLTAGE: + pwr |= SDHCI_POWER_180; + break; + case MMC_OCR_290_300: + case MMC_OCR_300_310: + pwr |= SDHCI_POWER_300; + break; + case MMC_OCR_320_330: + case MMC_OCR_330_340: + pwr |= SDHCI_POWER_330; + break; + } + WR1(slot, SDHCI_POWER_CONTROL, pwr); + /* Turn on the power. */ + pwr |= SDHCI_POWER_ON; + WR1(slot, SDHCI_POWER_CONTROL, pwr); +} + +static void +sdhci_read_block_pio(struct sdhci_slot *slot) +{ + uint32_t data; + char *buffer; + size_t left; + + buffer = slot->curcmd->data->data; + buffer += slot->offset; + /* Transfer one block at a time. */ + left = min(512, slot->curcmd->data->len - slot->offset); + slot->offset += left; + + /* If we are too fast, broken controllers return zeroes. */ + if (slot->sc->quirks & SDHCI_QUIRK_BROKEN_TIMINGS) + DELAY(10); + /* Handle unalligned and alligned buffer cases. */ + if ((intptr_t)buffer & 3) { + while (left > 3) { + data = RD4(slot, SDHCI_BUFFER); + buffer[0] = data; + buffer[1] = (data >> 8); + buffer[2] = (data >> 16); + buffer[3] = (data >> 24); + buffer += 4; + left -= 4; + } + } else { + bus_read_multi_stream_4(slot->mem_res, SDHCI_BUFFER, + (uint32_t *)buffer, left >> 2); + left &= 3; + } + /* Handle uneven size case. */ + if (left > 0) { + data = RD4(slot, SDHCI_BUFFER); + while (left > 0) { + *(buffer++) = data; + data >>= 8; + left--; + } + } +} + +static void +sdhci_write_block_pio(struct sdhci_slot *slot) +{ + uint32_t data = 0; + char *buffer; + size_t left; + + buffer = slot->curcmd->data->data; + buffer += slot->offset; + /* Transfer one block at a time. */ + left = min(512, slot->curcmd->data->len - slot->offset); + slot->offset += left; + + /* Handle unalligned and alligned 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; + WR4(slot, SDHCI_BUFFER, data); + } + } else { + bus_write_multi_stream_4(slot->mem_res, SDHCI_BUFFER, + (uint32_t *)buffer, left >> 2); + left &= 3; + } + /* Handle uneven size case. */ + if (left > 0) { + while (left > 0) { + data <<= 8; + data += *(buffer++); + left--; + } + WR4(slot, SDHCI_BUFFER, data); + } +} + +static void +sdhci_transfer_pio(struct sdhci_slot *slot) +{ + + /* Read as many blocks as possible. */ + if (slot->curcmd->data->flags & MMC_DATA_READ) { + while (RD4(slot, SDHCI_PRESENT_STATE) & + SDHCI_DATA_AVAILABLE) { + sdhci_read_block_pio(slot); + if (slot->offset >= slot->curcmd->data->len) + break; + } + } else { + while (RD4(slot, SDHCI_PRESENT_STATE) & + SDHCI_SPACE_AVAILABLE) { + sdhci_write_block_pio(slot); + if (slot->offset >= slot->curcmd->data->len) + break; + } + } +} + +static void +sdhci_card_delay(void *arg) +{ + struct sdhci_slot *slot = arg; + + taskqueue_enqueue(taskqueue_swi, &slot->card_task); +} + +static void +sdhci_card_task(void *arg, int pending) +{ + struct sdhci_slot *slot = arg; + + SDHCI_LOCK(slot); + if (RD4(slot, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT) { + if (slot->dev == NULL) { + /* If card is present - attach mmc bus. */ + slot->dev = device_add_child(slot->sc->dev, "mmc", -1); + device_set_ivars(slot->dev, slot); + SDHCI_UNLOCK(slot); + device_probe_and_attach(slot->dev); + } else + SDHCI_UNLOCK(slot); + } else { + if (slot->dev != NULL) { + /* If no card present - detach mmc bus. */ + device_t d = slot->dev; + slot->dev = NULL; + SDHCI_UNLOCK(slot); + device_delete_child(slot->sc->dev, d); + } else + SDHCI_UNLOCK(slot); + } +} + +static int +sdhci_probe(device_t dev) +{ + uint32_t model; + uint16_t subvendor; + uint8_t class, subclass; + int i, result; + + model = (uint32_t)pci_get_device(dev) << 16; + model |= (uint32_t)pci_get_vendor(dev) & 0x0000ffff; + subvendor = pci_get_subvendor(dev); + class = pci_get_class(dev); + subclass = pci_get_subclass(dev); + + result = ENXIO; + for (i = 0; sdhci_devices[i].model != 0; i++) { + if (sdhci_devices[i].model == model && + (sdhci_devices[i].subvendor == 0xffff || + sdhci_devices[i].subvendor == subvendor)) { + device_set_desc(dev, sdhci_devices[i].desc); + result = BUS_PROBE_DEFAULT; + break; + } + } + if (result == ENXIO && class == PCIC_BASEPERIPH && + subclass == PCIS_BASEPERIPH_SDHC) { + device_set_desc(dev, "Generic SD HCI"); + result = BUS_PROBE_GENERIC; + } + + return (result); +} + +static int +sdhci_attach(device_t dev) +{ + struct sdhci_softc *sc = device_get_softc(dev); + uint32_t model; + uint16_t subvendor; + uint8_t class, subclass, progif; + int err, slots, bar, i; + + sc->dev = dev; + model = (uint32_t)pci_get_device(dev) << 16; + model |= (uint32_t)pci_get_vendor(dev) & 0x0000ffff; + subvendor = pci_get_subvendor(dev); + class = pci_get_class(dev); + subclass = pci_get_subclass(dev); + progif = pci_get_progif(dev); + /* Apply chip specific quirks. */ + for (i = 0; sdhci_devices[i].model != 0; i++) { + if (sdhci_devices[i].model == model && + (sdhci_devices[i].subvendor == 0xffff || + sdhci_devices[i].subvendor == subvendor)) { + sc->quirks = sdhci_devices[i].quirks; + break; + } + } + /* Read slots info from PCI registers. */ + slots = pci_read_config(dev, PCI_SLOT_INFO, 1); + bar = PCI_SLOT_INFO_FIRST_BAR(slots); + slots = PCI_SLOT_INFO_SLOTS(slots); + if (slots > 6 || bar > 5) { + device_printf(dev, "Incorrect slots information (%d, %d).\n", + slots, bar); + return (EINVAL); + } + /* Allocate IRQ. */ + sc->irq_rid = 0; + sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irq_rid, + RF_SHAREABLE | RF_ACTIVE); + if (sc->irq_res == NULL) { + device_printf(dev, "Can't allocate IRQ\n"); + return (ENOMEM); + } + /* Scan all slots. */ + for (i = 0; i < slots; i++) { + struct sdhci_slot *slot = &sc->slots[sc->num_slots]; + uint32_t caps; + + SDHCI_LOCK_INIT(slot); + slot->sc = sc; + slot->num = sc->num_slots; + /* Allocate memory. */ + slot->mem_rid = PCIR_BAR(bar + i); + slot->mem_res = bus_alloc_resource(dev, + SYS_RES_MEMORY, &slot->mem_rid, 0ul, ~0ul, 0x100, RF_ACTIVE); + if (slot->mem_res == NULL) { + device_printf(dev, "Can't allocate memory\n"); + SDHCI_LOCK_DESTROY(slot); + continue; + } + /* Allocate DMA tag. */ + err = bus_dma_tag_create(NULL, + DMA_BLOCK_SIZE, 0, BUS_SPACE_MAXADDR_32BIT, + BUS_SPACE_MAXADDR, NULL, NULL, + DMA_BLOCK_SIZE, 1, DMA_BLOCK_SIZE, + BUS_DMA_ALLOCNOW, &slot->dmatag); + if (err != 0) { + device_printf(dev, "Can't create DMA tag\n"); + SDHCI_LOCK_DESTROY(slot); + continue; + } + /* Allocate DMA memory. */ + err = bus_dmamem_alloc(slot->dmatag, (void **)&slot->dmamem, + BUS_DMA_NOWAIT, &slot->dmamap); + if (err != 0) { + device_printf(dev, "Can't alloc DMA memory\n"); + SDHCI_LOCK_DESTROY(slot); + continue; + } + /* Map the memory. */ + err = bus_dmamap_load(slot->dmatag, slot->dmamap, + (void *)slot->dmamem, DMA_BLOCK_SIZE, + sdhci_getaddr, &slot->paddr, 0); + if (err != 0 || slot->paddr == 0) { + device_printf(dev, "Can't load DMA memory\n"); + SDHCI_LOCK_DESTROY(slot); + continue; + } + /* Initialize slot. */ + sdhci_init(slot); + caps = RD4(slot, SDHCI_CAPABILITIES); + /* Calculate base clock frequency. */ + slot->max_clk = + (caps & SDHCI_CLOCK_BASE_MASK) >> SDHCI_CLOCK_BASE_SHIFT; + if (slot->max_clk == 0) { + device_printf(dev, "Hardware doesn't specify base clock " + "frequency.\n"); + } + slot->max_clk *= 1000000; + /* Calculate timeout clock frequency. */ + slot->timeout_clk = + (caps & SDHCI_TIMEOUT_CLK_MASK) >> SDHCI_TIMEOUT_CLK_SHIFT; + if (slot->timeout_clk == 0) { + device_printf(dev, "Hardware doesn't specify timeout clock " + "frequency.\n"); + } + if (caps & SDHCI_TIMEOUT_CLK_UNIT) + slot->timeout_clk *= 1000; + + slot->host.f_min = slot->max_clk / 256; + slot->host.f_max = slot->max_clk; + slot->host.host_ocr = 0; + if (caps & SDHCI_CAN_VDD_330) + slot->host.host_ocr |= MMC_OCR_320_330 | MMC_OCR_330_340; + if (caps & SDHCI_CAN_VDD_300) + slot->host.host_ocr |= MMC_OCR_290_300 | MMC_OCR_300_310; + if (caps & SDHCI_CAN_VDD_180) + slot->host.host_ocr |= MMC_OCR_LOW_VOLTAGE; + if (slot->host.host_ocr == 0) { + device_printf(dev, "Hardware doesn't report any " + "support voltages.\n"); + } + slot->host.caps = MMC_CAP_4_BIT_DATA; + if (caps & SDHCI_CAN_DO_HISPD) + slot->host.caps |= MMC_CAP_HSPEED; + /* Decide if we have usable DMA. */ + if (caps & SDHCI_CAN_DO_DMA) + slot->opt |= SDHCI_HAVE_DMA; + if (class == PCIC_BASEPERIPH && + subclass == PCIS_BASEPERIPH_SDHC && + progif != PCI_SDHCI_IFDMA) + slot->opt &= ~SDHCI_HAVE_DMA; + if (sc->quirks & SDHCI_QUIRK_BROKEN_DMA) + slot->opt &= ~SDHCI_HAVE_DMA; + if (sc->quirks & SDHCI_QUIRK_FORCE_DMA) + slot->opt |= SDHCI_HAVE_DMA; + + if (bootverbose || sdhci_debug) { + slot_printf(slot, "%uMHz%s 4bits%s%s%s %s\n", + slot->max_clk / 1000000, + (caps & SDHCI_CAN_DO_HISPD) ? " HS" : "", + (caps & SDHCI_CAN_VDD_330) ? " 3.3V" : "", + (caps & SDHCI_CAN_VDD_300) ? " 3.0V" : "", + (caps & SDHCI_CAN_VDD_180) ? " 1.8V" : "", + (slot->opt & SDHCI_HAVE_DMA) ? "DMA" : "PIO"); + sdhci_dumpregs(slot); + } + + TASK_INIT(&slot->card_task, 0, sdhci_card_task, slot); + callout_init(&slot->card_callout); + sc->num_slots++; + } + device_printf(dev, "%d slot(s) allocated\n", sc->num_slots); + /* Activate the interrupt */ + err = bus_setup_intr(dev, sc->irq_res, INTR_MPSAFE, sdhci_intr, sc, &sc->intrhand, NULL); + if (err) + device_printf(dev, "Can't setup IRQ\n"); + pci_enable_busmaster(dev); + /* Process cards detection. */ + for (i = 0; i < sc->num_slots; i++) { + struct sdhci_slot *slot = &sc->slots[i]; + + sdhci_card_task(slot, 0); + } + + return (0); +} + +static int +sdhci_detach(device_t dev) +{ + struct sdhci_softc *sc = device_get_softc(dev); + int i; + + bus_teardown_intr(dev, sc->irq_res, sc->intrhand); + bus_release_resource(dev, SYS_RES_IRQ, + sc->irq_rid, sc->irq_res); + + for (i = 0; i < sc->num_slots; i++) { + struct sdhci_slot *slot = &sc->slots[i]; + device_t d; + +#if 0 + /* XXX: need implementation of callout_drain or workaround */ + callout_drain(&slot->card_callout); +#endif + taskqueue_drain(taskqueue_swi, &slot->card_task); + + SDHCI_LOCK(slot); + d = slot->dev; + slot->dev = NULL; + SDHCI_UNLOCK(slot); + if (d != NULL) + device_delete_child(dev, d); + + SDHCI_LOCK(slot); + sdhci_reset(slot, SDHCI_RESET_ALL); + SDHCI_UNLOCK(slot); + bus_dmamap_unload(slot->dmatag, slot->dmamap); + bus_dmamem_free(slot->dmatag, slot->dmamem, slot->dmamap); + bus_dma_tag_destroy(slot->dmatag); + bus_release_resource(dev, SYS_RES_MEMORY, + slot->mem_rid, slot->mem_res); + SDHCI_LOCK_DESTROY(slot); + } + return (0); +} + +static int +sdhci_suspend(device_t dev) +{ + struct sdhci_softc *sc = device_get_softc(dev); + int i, err; + + err = bus_generic_suspend(dev); + if (err) + return (err); + for (i = 0; i < sc->num_slots; i++) + sdhci_reset(&sc->slots[i], SDHCI_RESET_ALL); + return (0); +} + +static int +sdhci_resume(device_t dev) +{ + struct sdhci_softc *sc = device_get_softc(dev); + int i; + + for (i = 0; i < sc->num_slots; i++) + sdhci_init(&sc->slots[i]); + return (bus_generic_resume(dev)); +} + +static int +sdhci_update_ios(device_t brdev, device_t reqdev) +{ + struct sdhci_slot *slot = device_get_ivars(reqdev); + struct mmc_ios *ios = &slot->host.ios; + + SDHCI_LOCK(slot); + /* Do full reset on bus power down to clear from any state. */ + if (ios->power_mode == power_off) { + WR4(slot, SDHCI_SIGNAL_ENABLE, 0); + sdhci_init(slot); + } + /* Configure the bus. */ + sdhci_set_clock(slot, ios->clock); + sdhci_set_power(slot, (ios->power_mode == power_off)?0:ios->vdd); + if (ios->bus_width == bus_width_4) + slot->hostctrl |= SDHCI_CTRL_4BITBUS; + else + slot->hostctrl &= ~SDHCI_CTRL_4BITBUS; + if (ios->timing == bus_timing_hs) + slot->hostctrl |= SDHCI_CTRL_HISPD; + else + slot->hostctrl &= ~SDHCI_CTRL_HISPD; + WR1(slot, SDHCI_HOST_CONTROL, slot->hostctrl); + /* Some controllers like reset after bus changes. */ + if(slot->sc->quirks & SDHCI_QUIRK_RESET_ON_IOS) + sdhci_reset(slot, SDHCI_RESET_CMD | SDHCI_RESET_DATA); + + SDHCI_UNLOCK(slot); + return (0); +} + +static void +sdhci_set_transfer_mode(struct sdhci_slot *slot, + struct mmc_data *data) +{ + uint16_t mode; + + if (data == NULL) + return; + + mode = SDHCI_TRNS_BLK_CNT_EN; + if (data->len > 512) + mode |= SDHCI_TRNS_MULTI; + if (data->flags & MMC_DATA_READ) + mode |= SDHCI_TRNS_READ; + if (slot->req->stop) + mode |= SDHCI_TRNS_ACMD12; + if (slot->flags & SDHCI_USE_DMA) + mode |= SDHCI_TRNS_DMA; + + WR2(slot, SDHCI_TRANSFER_MODE, mode); +} + +static void +sdhci_start_command(struct sdhci_slot *slot, struct mmc_command *cmd) +{ + struct mmc_request *req = slot->req; + int flags, timeout; + uint32_t mask, state; + + slot->curcmd = cmd; + slot->cmd_done = 0; + + cmd->error = MMC_ERR_NONE; + + /* This flags combination is not supported by controller. */ + if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) { + slot_printf(slot, "Unsupported response type!\n"); + cmd->error = MMC_ERR_FAILED; + slot->req = NULL; + slot->curcmd = NULL; + req->done(req); + return; + } + + /* Read controller present state. */ + state = RD4(slot, SDHCI_PRESENT_STATE); + /* Do not issue command if there is no card, clock or power. + * Controller will not detect timeout without clock active. */ + if ((state & SDHCI_CARD_PRESENT) == 0 || + slot->power == 0 || + slot->clock == 0) { + cmd->error = MMC_ERR_FAILED; + slot->req = NULL; + slot->curcmd = NULL; + req->done(req); + return; + } + /* Always wait for free CMD bus. */ + mask = SDHCI_CMD_INHIBIT; + /* Wait for free DAT if we have data or busy signal. */ + if (cmd->data || (cmd->flags & MMC_RSP_BUSY)) + mask |= SDHCI_DAT_INHIBIT; + /* We shouldn't wait for DAT for stop commands. */ + if (cmd == slot->req->stop) + mask &= ~SDHCI_DAT_INHIBIT; + /* Wait for bus no more then 10 ms. */ + timeout = 10; + while (state & mask) { + if (timeout == 0) { + slot_printf(slot, "Controller never released " + "inhibit bit(s).\n"); + sdhci_dumpregs(slot); + cmd->error = MMC_ERR_FAILED; + slot->req = NULL; + slot->curcmd = NULL; + req->done(req); + return; + } + timeout--; + DELAY(1000); + state = RD4(slot, SDHCI_PRESENT_STATE); + } + + /* Prepare command flags. */ + if (!(cmd->flags & MMC_RSP_PRESENT)) + flags = SDHCI_CMD_RESP_NONE; + else if (cmd->flags & MMC_RSP_136) + flags = SDHCI_CMD_RESP_LONG; + else if (cmd->flags & MMC_RSP_BUSY) + flags = SDHCI_CMD_RESP_SHORT_BUSY; + else + flags = SDHCI_CMD_RESP_SHORT; + if (cmd->flags & MMC_RSP_CRC) + flags |= SDHCI_CMD_CRC; + if (cmd->flags & MMC_RSP_OPCODE) + flags |= SDHCI_CMD_INDEX; + if (cmd->data) + flags |= SDHCI_CMD_DATA; + if (cmd->opcode == MMC_STOP_TRANSMISSION) + flags |= SDHCI_CMD_TYPE_ABORT; + /* Prepare data. */ + sdhci_start_data(slot, cmd->data); + /* + * Interrupt aggregation: To reduce total number of interrupts + * group response interrupt with data interrupt when possible. + * If there going to be data interrupt, mask response one. + */ + if (slot->data_done == 0) { + WR4(slot, SDHCI_SIGNAL_ENABLE, + slot->intmask &= ~SDHCI_INT_RESPONSE); + } + /* Set command argument. */ + WR4(slot, SDHCI_ARGUMENT, cmd->arg); + /* Set data transfer mode. */ + sdhci_set_transfer_mode(slot, cmd->data); + /* Set command flags. */ + WR1(slot, SDHCI_COMMAND_FLAGS, flags); + /* Start command. */ + WR1(slot, SDHCI_COMMAND, cmd->opcode); +} + +static void +sdhci_finish_command(struct sdhci_slot *slot) +{ + int i; + + slot->cmd_done = 1; + /* Interrupt aggregation: Restore command interrupt. + * Main restore point for the case when command interrupt + * happened first. */ + WR4(slot, SDHCI_SIGNAL_ENABLE, slot->intmask |= SDHCI_INT_RESPONSE); + /* In case of error - reset host and return. */ + if (slot->curcmd->error) { + sdhci_reset(slot, SDHCI_RESET_CMD); + sdhci_reset(slot, SDHCI_RESET_DATA); + sdhci_start(slot); + return; + } + /* If command has response - fetch it. */ + if (slot->curcmd->flags & MMC_RSP_PRESENT) { + if (slot->curcmd->flags & MMC_RSP_136) { + /* CRC is stripped so we need one byte shift. */ + uint8_t extra = 0; + for (i = 0; i < 4; i++) { + uint32_t val = RD4(slot, SDHCI_RESPONSE + i * 4); + slot->curcmd->resp[3 - i] = (val << 8) + extra; + extra = val >> 24; + } + } else + slot->curcmd->resp[0] = RD4(slot, SDHCI_RESPONSE); + } + /* If data ready - finish. */ + if (slot->data_done) + sdhci_start(slot); +} + +static void +sdhci_start_data(struct sdhci_slot *slot, struct mmc_data *data) +{ + uint32_t target_timeout, current_timeout; + uint8_t div; + + if (data == NULL && (slot->curcmd->flags & MMC_RSP_BUSY) == 0) { + slot->data_done = 1; + return; + } + + slot->data_done = 0; + + /* Calculate and set data timeout.*/ + /* XXX: We should have this from mmc layer, now assume 1 sec. */ + target_timeout = 1000000; + div = 0; + current_timeout = (1 << 13) * 1000 / slot->timeout_clk; + while (current_timeout < target_timeout) { + div++; + current_timeout <<= 1; + if (div >= 0xF) + break; + } + /* Compensate for an off-by-one error in the CaFe chip.*/ + if (slot->sc->quirks & SDHCI_QUIRK_INCR_TIMEOUT_CONTROL) + div++; + if (div >= 0xF) { + slot_printf(slot, "Timeout too large!\n"); + div = 0xE; + } + WR1(slot, SDHCI_TIMEOUT_CONTROL, div); + + if (data == NULL) + return; + + /* Use DMA if possible. */ + if ((slot->opt & SDHCI_HAVE_DMA)) + slot->flags |= SDHCI_USE_DMA; + /* If data is small, broken DMA may return zeroes instead of data, */ + if ((slot->sc->quirks & SDHCI_QUIRK_BROKEN_TIMINGS) && + (data->len <= 512)) + slot->flags &= ~SDHCI_USE_DMA; + /* Some controllers require even block sizes. */ + if ((slot->sc->quirks & SDHCI_QUIRK_32BIT_DMA_SIZE) && + ((data->len) & 0x3)) + slot->flags &= ~SDHCI_USE_DMA; + /* Load DMA buffer. */ + if (slot->flags & SDHCI_USE_DMA) { + if (data->flags & MMC_DATA_READ) { + bus_dmamap_sync(slot->dmatag, slot->dmamap, BUS_DMASYNC_PREREAD); + } else { + memcpy(slot->dmamem, data->data, + (data->len < DMA_BLOCK_SIZE)?data->len:DMA_BLOCK_SIZE); + bus_dmamap_sync(slot->dmatag, slot->dmamap, BUS_DMASYNC_PREWRITE); + } + WR4(slot, SDHCI_DMA_ADDRESS, slot->paddr); + /* Interrupt aggregation: Mask border interrupt + * for the last page and unmask else. */ + if (data->len == DMA_BLOCK_SIZE) + slot->intmask &= ~SDHCI_INT_DMA_END; + else + slot->intmask |= SDHCI_INT_DMA_END; + WR4(slot, SDHCI_SIGNAL_ENABLE, slot->intmask); + } + /* Current data offset for both PIO and DMA. */ + slot->offset = 0; + /* Set block size and request IRQ on 4K border. */ + WR2(slot, SDHCI_BLOCK_SIZE, + SDHCI_MAKE_BLKSZ(DMA_BOUNDARY, (data->len < 512)?data->len:512)); + /* Set block count. */ + WR2(slot, SDHCI_BLOCK_COUNT, (data->len + 511) / 512); +} + +static void +sdhci_finish_data(struct sdhci_slot *slot) +{ + struct mmc_data *data = slot->curcmd->data; + + slot->data_done = 1; + /* Interrupt aggregation: Restore command interrupt. + * Auxillary restore point for the case when data interrupt + * happened first. */ + if (!slot->cmd_done) { + WR4(slot, SDHCI_SIGNAL_ENABLE, + slot->intmask |= SDHCI_INT_RESPONSE); + } + /* Unload rest of data from DMA buffer. */ + if (slot->flags & SDHCI_USE_DMA) { + if (data->flags & MMC_DATA_READ) { + size_t left = data->len - slot->offset; + bus_dmamap_sync(slot->dmatag, slot->dmamap, BUS_DMASYNC_POSTREAD); + memcpy((u_char*)data->data + slot->offset, slot->dmamem, + (left < DMA_BLOCK_SIZE)?left:DMA_BLOCK_SIZE); + } else + bus_dmamap_sync(slot->dmatag, slot->dmamap, BUS_DMASYNC_POSTWRITE); + } + /* If there was error - reset the host. */ + if (slot->curcmd->error) { + sdhci_reset(slot, SDHCI_RESET_CMD); + sdhci_reset(slot, SDHCI_RESET_DATA); + sdhci_start(slot); + return; + } + /* If we already have command response - finish. */ + if (slot->cmd_done) + sdhci_start(slot); +} + +static void +sdhci_start(struct sdhci_slot *slot) +{ + struct mmc_request *req; + + req = slot->req; + if (req == NULL) + return; + + if (!(slot->flags & CMD_STARTED)) { + slot->flags |= CMD_STARTED; + sdhci_start_command(slot, req->cmd); + return; + } +/* We don't need this until using Auto-CMD12 feature + if (!(slot->flags & STOP_STARTED) && req->stop) { + slot->flags |= STOP_STARTED; + sdhci_start_command(slot, req->stop); + return; + } +*/ + if (sdhci_debug > 1) + slot_printf(slot, "result: %d\n", req->cmd->error); + if (!req->cmd->error && + (slot->sc->quirks & SDHCI_QUIRK_RESET_AFTER_REQUEST)) { + sdhci_reset(slot, SDHCI_RESET_CMD); + sdhci_reset(slot, SDHCI_RESET_DATA); + } + + /* We must be done -- bad idea to do this while locked? */ + slot->req = NULL; + slot->curcmd = NULL; + req->done(req); +} + +static int +sdhci_request(device_t brdev, device_t reqdev, struct mmc_request *req) +{ + struct sdhci_slot *slot = device_get_ivars(reqdev); + + SDHCI_LOCK(slot); + if (slot->req != NULL) { + SDHCI_UNLOCK(slot); + return (EBUSY); + } + if (sdhci_debug > 1) { + slot_printf(slot, "CMD%u arg %#x flags %#x dlen %u dflags %#x\n", + req->cmd->opcode, req->cmd->arg, req->cmd->flags, + (req->cmd->data)?(u_int)req->cmd->data->len:0, + (req->cmd->data)?req->cmd->data->flags:0); + } + slot->req = req; + slot->flags = 0; + sdhci_start(slot); + SDHCI_UNLOCK(slot); + if (dumping) { + while (slot->req != NULL) { + sdhci_intr(slot->sc); + DELAY(10); + } + } + return (0); +} + +static int +sdhci_get_ro(device_t brdev, device_t reqdev) +{ + struct sdhci_slot *slot = device_get_ivars(reqdev); + uint32_t val; + + SDHCI_LOCK(slot); + val = RD4(slot, SDHCI_PRESENT_STATE); + SDHCI_UNLOCK(slot); + return (!(val & SDHCI_WRITE_PROTECT)); +} + +static int +sdhci_acquire_host(device_t brdev, device_t reqdev) +{ + struct sdhci_slot *slot = device_get_ivars(reqdev); + int err = 0; + + SDHCI_LOCK(slot); + while (slot->bus_busy) + lksleep(slot, &slot->lock, 0, "sdhciah", 0); + slot->bus_busy++; + /* Activate led. */ + WR1(slot, SDHCI_HOST_CONTROL, slot->hostctrl |= SDHCI_CTRL_LED); + SDHCI_UNLOCK(slot); + return (err); +} + +static int +sdhci_release_host(device_t brdev, device_t reqdev) +{ + struct sdhci_slot *slot = device_get_ivars(reqdev); + + SDHCI_LOCK(slot); + /* Deactivate led. */ + WR1(slot, SDHCI_HOST_CONTROL, slot->hostctrl &= ~SDHCI_CTRL_LED); + slot->bus_busy--; + SDHCI_UNLOCK(slot); + wakeup(slot); + return (0); +} + +static void +sdhci_cmd_irq(struct sdhci_slot *slot, uint32_t intmask) +{ + + if (!slot->curcmd) { + slot_printf(slot, "Got command interrupt 0x%08x, but " + "there is no active command.\n", intmask); + sdhci_dumpregs(slot); + return; + } + if (intmask & SDHCI_INT_TIMEOUT) + slot->curcmd->error = MMC_ERR_TIMEOUT; + else if (intmask & SDHCI_INT_CRC) + slot->curcmd->error = MMC_ERR_BADCRC; + else if (intmask & (SDHCI_INT_END_BIT | SDHCI_INT_INDEX)) + slot->curcmd->error = MMC_ERR_FIFO; + + sdhci_finish_command(slot); +} + +static void +sdhci_data_irq(struct sdhci_slot *slot, uint32_t intmask) +{ + + if (!slot->curcmd) { + slot_printf(slot, "Got data interrupt 0x%08x, but " + "there is no active command.\n", intmask); + sdhci_dumpregs(slot); + return; + } + if (slot->curcmd->data == NULL && + (slot->curcmd->flags & MMC_RSP_BUSY) == 0) { + slot_printf(slot, "Got data interrupt 0x%08x, but " + "there is no active data operation.\n", + intmask); + sdhci_dumpregs(slot); + return; + } + if (intmask & SDHCI_INT_DATA_TIMEOUT) + slot->curcmd->error = MMC_ERR_TIMEOUT; + else if (intmask & (SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_END_BIT)) + slot->curcmd->error = MMC_ERR_BADCRC; + if (slot->curcmd->data == NULL && + (intmask & (SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL | + SDHCI_INT_DMA_END))) { + slot_printf(slot, "Got data interrupt 0x%08x, but " + "there is busy-only command.\n", intmask); + sdhci_dumpregs(slot); + slot->curcmd->error = MMC_ERR_INVALID; + } + if (slot->curcmd->error) { + /* No need to continue after any error. */ + sdhci_finish_data(slot); + return; + } + + /* Handle PIO interrupt. */ + if (intmask & (SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL)) + sdhci_transfer_pio(slot); + /* Handle DMA border. */ + if (intmask & SDHCI_INT_DMA_END) { + struct mmc_data *data = slot->curcmd->data; + size_t left; + + /* Unload DMA buffer... */ + left = data->len - slot->offset; + if (data->flags & MMC_DATA_READ) { + bus_dmamap_sync(slot->dmatag, slot->dmamap, + BUS_DMASYNC_POSTREAD); + memcpy((u_char*)data->data + slot->offset, slot->dmamem, + (left < DMA_BLOCK_SIZE)?left:DMA_BLOCK_SIZE); + } else { + bus_dmamap_sync(slot->dmatag, slot->dmamap, + BUS_DMASYNC_POSTWRITE); + } + /* ... and reload it again. */ + slot->offset += DMA_BLOCK_SIZE; + left = data->len - slot->offset; + if (data->flags & MMC_DATA_READ) { + bus_dmamap_sync(slot->dmatag, slot->dmamap, + BUS_DMASYNC_PREREAD); + } else { + memcpy(slot->dmamem, (u_char*)data->data + slot->offset, + (left < DMA_BLOCK_SIZE)?left:DMA_BLOCK_SIZE); + bus_dmamap_sync(slot->dmatag, slot->dmamap, + BUS_DMASYNC_PREWRITE); + } + /* Interrupt aggregation: Mask border interrupt + * for the last page. */ + if (left == DMA_BLOCK_SIZE) { + slot->intmask &= ~SDHCI_INT_DMA_END; + WR4(slot, SDHCI_SIGNAL_ENABLE, slot->intmask); + } + /* Restart DMA. */ + WR4(slot, SDHCI_DMA_ADDRESS, slot->paddr); + } + /* We have got all data. */ + if (intmask & SDHCI_INT_DATA_END) + sdhci_finish_data(slot); +} + +static void +sdhci_acmd_irq(struct sdhci_slot *slot) +{ + uint16_t err; + + err = RD4(slot, SDHCI_ACMD12_ERR); + if (!slot->curcmd) { + slot_printf(slot, "Got AutoCMD12 error 0x%04x, but " + "there is no active command.\n", err); + sdhci_dumpregs(slot); + return; + } + slot_printf(slot, "Got AutoCMD12 error 0x%04x\n", err); + sdhci_reset(slot, SDHCI_RESET_CMD); +} + +static void +sdhci_intr(void *arg) +{ + struct sdhci_softc *sc = (struct sdhci_softc *)arg; + int i; + + for (i = 0; i < sc->num_slots; i++) { + struct sdhci_slot *slot = &sc->slots[i]; + uint32_t intmask; + + SDHCI_LOCK(slot); + /* Read slot interrupt status. */ + intmask = RD4(slot, SDHCI_INT_STATUS); + if (intmask == 0 || intmask == 0xffffffff) { + SDHCI_UNLOCK(slot); + continue; + } + if (sdhci_debug > 2) + slot_printf(slot, "Interrupt %#x\n", intmask); + + /* Handle card presence interrupts. */ + if (intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) { + WR4(slot, SDHCI_INT_STATUS, intmask & + (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)); + + if (intmask & SDHCI_INT_CARD_REMOVE) { + if (bootverbose || sdhci_debug) + slot_printf(slot, "Card removed\n"); + callout_stop(&slot->card_callout); + taskqueue_enqueue(taskqueue_swi, + &slot->card_task); + } + if (intmask & SDHCI_INT_CARD_INSERT) { + if (bootverbose || sdhci_debug) + slot_printf(slot, "Card inserted\n"); + callout_reset(&slot->card_callout, hz / 2, + sdhci_card_delay, slot); + } + intmask &= ~(SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE); + } + /* Handle command interrupts. */ + if (intmask & SDHCI_INT_CMD_MASK) { + WR4(slot, SDHCI_INT_STATUS, intmask & SDHCI_INT_CMD_MASK); + sdhci_cmd_irq(slot, intmask & SDHCI_INT_CMD_MASK); + } + /* Handle data interrupts. */ + if (intmask & SDHCI_INT_DATA_MASK) { + WR4(slot, SDHCI_INT_STATUS, intmask & SDHCI_INT_DATA_MASK); + sdhci_data_irq(slot, intmask & SDHCI_INT_DATA_MASK); + } + /* Handle AutoCMD12 error interrupt. */ + if (intmask & SDHCI_INT_ACMD12ERR) { + WR4(slot, SDHCI_INT_STATUS, SDHCI_INT_ACMD12ERR); + sdhci_acmd_irq(slot); + } + intmask &= ~(SDHCI_INT_CMD_MASK | SDHCI_INT_DATA_MASK); + intmask &= ~SDHCI_INT_ACMD12ERR; + intmask &= ~SDHCI_INT_ERROR; + /* Handle bus power interrupt. */ + if (intmask & SDHCI_INT_BUS_POWER) { + WR4(slot, SDHCI_INT_STATUS, SDHCI_INT_BUS_POWER); + slot_printf(slot, + "Card is consuming too much power!\n"); + intmask &= ~SDHCI_INT_BUS_POWER; + } + /* The rest is unknown. */ + if (intmask) { + WR4(slot, SDHCI_INT_STATUS, intmask); + slot_printf(slot, "Unexpected interrupt 0x%08x.\n", + intmask); + sdhci_dumpregs(slot); + } + + SDHCI_UNLOCK(slot); + } +} + +static int +sdhci_read_ivar(device_t bus, device_t child, int which, uintptr_t *result) +{ + struct sdhci_slot *slot = device_get_ivars(child); + + switch (which) { + default: + return (EINVAL); + case MMCBR_IVAR_BUS_MODE: + *(int *)result = slot->host.ios.bus_mode; + break; + case MMCBR_IVAR_BUS_WIDTH: + *(int *)result = slot->host.ios.bus_width; + break; + case MMCBR_IVAR_CHIP_SELECT: + *(int *)result = slot->host.ios.chip_select; + break; + case MMCBR_IVAR_CLOCK: + *(int *)result = slot->host.ios.clock; + break; + case MMCBR_IVAR_F_MIN: + *(int *)result = slot->host.f_min; + break; + case MMCBR_IVAR_F_MAX: + *(int *)result = slot->host.f_max; + break; + case MMCBR_IVAR_HOST_OCR: + *(int *)result = slot->host.host_ocr; + break; + case MMCBR_IVAR_MODE: + *(int *)result = slot->host.mode; + break; + case MMCBR_IVAR_OCR: + *(int *)result = slot->host.ocr; + break; + case MMCBR_IVAR_POWER_MODE: + *(int *)result = slot->host.ios.power_mode; + break; + case MMCBR_IVAR_VDD: + *(int *)result = slot->host.ios.vdd; + break; + case MMCBR_IVAR_CAPS: + *(int *)result = slot->host.caps; + break; + case MMCBR_IVAR_TIMING: + *(int *)result = slot->host.ios.timing; + break; + case MMCBR_IVAR_MAX_DATA: + *(int *)result = 65535; + break; + } + return (0); +} + +static int +sdhci_write_ivar(device_t bus, device_t child, int which, uintptr_t value) +{ + struct sdhci_slot *slot = device_get_ivars(child); + + switch (which) { + default: + return (EINVAL); + case MMCBR_IVAR_BUS_MODE: + slot->host.ios.bus_mode = value; + break; + case MMCBR_IVAR_BUS_WIDTH: + slot->host.ios.bus_width = value; + break; + case MMCBR_IVAR_CHIP_SELECT: + slot->host.ios.chip_select = value; + break; + case MMCBR_IVAR_CLOCK: + if (value > 0) { + uint32_t clock = slot->max_clk; + int i; + + for (i = 0; i < 8; i++) { + if (clock <= value) + break; + clock >>= 1; + } + slot->host.ios.clock = clock; + } else + slot->host.ios.clock = 0; + break; + case MMCBR_IVAR_MODE: + slot->host.mode = value; + break; + case MMCBR_IVAR_OCR: + slot->host.ocr = value; + break; + case MMCBR_IVAR_POWER_MODE: + slot->host.ios.power_mode = value; + break; + case MMCBR_IVAR_VDD: + slot->host.ios.vdd = value; + break; + case MMCBR_IVAR_TIMING: + slot->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: + return (EINVAL); + } + return (0); +} + +static device_method_t sdhci_methods[] = { + /* device_if */ + DEVMETHOD(device_probe, sdhci_probe), + DEVMETHOD(device_attach, sdhci_attach), + DEVMETHOD(device_detach, sdhci_detach), + DEVMETHOD(device_suspend, sdhci_suspend), + DEVMETHOD(device_resume, sdhci_resume), + + /* Bus interface */ + DEVMETHOD(bus_read_ivar, sdhci_read_ivar), + DEVMETHOD(bus_write_ivar, sdhci_write_ivar), + + /* mmcbr_if */ + DEVMETHOD(mmcbr_update_ios, sdhci_update_ios), + DEVMETHOD(mmcbr_request, sdhci_request), + DEVMETHOD(mmcbr_get_ro, sdhci_get_ro), + DEVMETHOD(mmcbr_acquire_host, sdhci_acquire_host), + DEVMETHOD(mmcbr_release_host, sdhci_release_host), + + {0, 0}, +}; + +static driver_t sdhci_driver = { + "sdhci", + sdhci_methods, + sizeof(struct sdhci_softc), +}; +static devclass_t sdhci_devclass; + + +DRIVER_MODULE(sdhci, pci, sdhci_driver, sdhci_devclass, 0, 0); diff --git a/sys/dev/disk/sdhci/sdhci.h b/sys/dev/disk/sdhci/sdhci.h new file mode 100644 index 0000000000..b25fced660 --- /dev/null +++ b/sys/dev/disk/sdhci/sdhci.h @@ -0,0 +1,190 @@ +/*- + * Copyright (c) 2008 Alexander Motin + * 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 ``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 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. + * + * $FreeBSD: src/sys/dev/sdhci/sdhci.h,v 1.1 2008/10/21 20:33:40 mav Exp $ + */ + +/* + * PCI registers + */ + +#define PCI_SDHCI_IFPIO 0x00 +#define PCI_SDHCI_IFDMA 0x01 +#define PCI_SDHCI_IFVENDOR 0x02 + +#define PCI_SLOT_INFO 0x40 /* 8 bits */ +#define PCI_SLOT_INFO_SLOTS(x) (((x >> 4) & 7) + 1) +#define PCI_SLOT_INFO_FIRST_BAR(x) ((x) & 7) + +/* + * Controller registers + */ + +#define SDHCI_DMA_ADDRESS 0x00 + +#define SDHCI_BLOCK_SIZE 0x04 +#define SDHCI_MAKE_BLKSZ(dma, blksz) (((dma & 0x7) << 12) | (blksz & 0xFFF)) + +#define SDHCI_BLOCK_COUNT 0x06 + +#define SDHCI_ARGUMENT 0x08 + +#define SDHCI_TRANSFER_MODE 0x0C +#define SDHCI_TRNS_DMA 0x01 +#define SDHCI_TRNS_BLK_CNT_EN 0x02 +#define SDHCI_TRNS_ACMD12 0x04 +#define SDHCI_TRNS_READ 0x10 +#define SDHCI_TRNS_MULTI 0x20 + +#define SDHCI_COMMAND_FLAGS 0x0E +#define SDHCI_CMD_RESP_NONE 0x00 +#define SDHCI_CMD_RESP_LONG 0x01 +#define SDHCI_CMD_RESP_SHORT 0x02 +#define SDHCI_CMD_RESP_SHORT_BUSY 0x03 +#define SDHCI_CMD_RESP_MASK 0x03 +#define SDHCI_CMD_CRC 0x08 +#define SDHCI_CMD_INDEX 0x10 +#define SDHCI_CMD_DATA 0x20 +#define SDHCI_CMD_TYPE_NORMAL 0x00 +#define SDHCI_CMD_TYPE_SUSPEND 0x40 +#define SDHCI_CMD_TYPE_RESUME 0x80 +#define SDHCI_CMD_TYPE_ABORT 0xc0 +#define SDHCI_CMD_TYPE_MASK 0xc0 + +#define SDHCI_COMMAND 0x0F + +#define SDHCI_RESPONSE 0x10 + +#define SDHCI_BUFFER 0x20 + +#define SDHCI_PRESENT_STATE 0x24 +#define SDHCI_CMD_INHIBIT 0x00000001 +#define SDHCI_DAT_INHIBIT 0x00000002 +#define SDHCI_DAT_ACTIVE 0x00000004 +#define SDHCI_DOING_WRITE 0x00000100 +#define SDHCI_DOING_READ 0x00000200 +#define SDHCI_SPACE_AVAILABLE 0x00000400 +#define SDHCI_DATA_AVAILABLE 0x00000800 +#define SDHCI_CARD_PRESENT 0x00010000 +#define SDHCI_CARD_STABLE 0x00020000 +#define SDHCI_CARD_PIN 0x00040000 +#define SDHCI_WRITE_PROTECT 0x00080000 +#define SDHCI_STATE_DAT 0x00700000 +#define SDHCI_STATE_CMD 0x00800000 + +#define SDHCI_HOST_CONTROL 0x28 +#define SDHCI_CTRL_LED 0x01 +#define SDHCI_CTRL_4BITBUS 0x02 +#define SDHCI_CTRL_HISPD 0x04 +#define SDHCI_CTRL_SDMA 0x08 +#define SDHCI_CTRL_ADMA2 0x10 +#define SDHCI_CTRL_ADMA264 0x18 +#define SDHCI_CTRL_CARD_DET 0x40 +#define SDHCI_CTRL_FORCE_CARD 0x80 + +#define SDHCI_POWER_CONTROL 0x29 +#define SDHCI_POWER_ON 0x01 +#define SDHCI_POWER_180 0x0A +#define SDHCI_POWER_300 0x0C +#define SDHCI_POWER_330 0x0E + +#define SDHCI_BLOCK_GAP_CONTROL 0x2A + +#define SDHCI_WAKE_UP_CONTROL 0x2B + +#define SDHCI_CLOCK_CONTROL 0x2C +#define SDHCI_DIVIDER_SHIFT 8 +#define SDHCI_CLOCK_CARD_EN 0x0004 +#define SDHCI_CLOCK_INT_STABLE 0x0002 +#define SDHCI_CLOCK_INT_EN 0x0001 + +#define SDHCI_TIMEOUT_CONTROL 0x2E + +#define SDHCI_SOFTWARE_RESET 0x2F +#define SDHCI_RESET_ALL 0x01 +#define SDHCI_RESET_CMD 0x02 +#define SDHCI_RESET_DATA 0x04 + +#define SDHCI_INT_STATUS 0x30 +#define SDHCI_INT_ENABLE 0x34 +#define SDHCI_SIGNAL_ENABLE 0x38 +#define SDHCI_INT_RESPONSE 0x00000001 +#define SDHCI_INT_DATA_END 0x00000002 +#define SDHCI_INT_BLOCK_GAP 0x00000004 +#define SDHCI_INT_DMA_END 0x00000008 +#define SDHCI_INT_SPACE_AVAIL 0x00000010 +#define SDHCI_INT_DATA_AVAIL 0x00000020 +#define SDHCI_INT_CARD_INSERT 0x00000040 +#define SDHCI_INT_CARD_REMOVE 0x00000080 +#define SDHCI_INT_CARD_INT 0x00000100 +#define SDHCI_INT_ERROR 0x00008000 +#define SDHCI_INT_TIMEOUT 0x00010000 +#define SDHCI_INT_CRC 0x00020000 +#define SDHCI_INT_END_BIT 0x00040000 +#define SDHCI_INT_INDEX 0x00080000 +#define SDHCI_INT_DATA_TIMEOUT 0x00100000 +#define SDHCI_INT_DATA_CRC 0x00200000 +#define SDHCI_INT_DATA_END_BIT 0x00400000 +#define SDHCI_INT_BUS_POWER 0x00800000 +#define SDHCI_INT_ACMD12ERR 0x01000000 +#define SDHCI_INT_ADMAERR 0x02000000 + +#define SDHCI_INT_NORMAL_MASK 0x00007FFF +#define SDHCI_INT_ERROR_MASK 0xFFFF8000 + +#define SDHCI_INT_CMD_MASK (SDHCI_INT_RESPONSE | SDHCI_INT_TIMEOUT | \ + SDHCI_INT_CRC | SDHCI_INT_END_BIT | SDHCI_INT_INDEX) +#define SDHCI_INT_DATA_MASK (SDHCI_INT_DATA_END | SDHCI_INT_DMA_END | \ + SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL | \ + SDHCI_INT_DATA_TIMEOUT | SDHCI_INT_DATA_CRC | \ + SDHCI_INT_DATA_END_BIT) + +#define SDHCI_ACMD12_ERR 0x3C + +#define SDHCI_CAPABILITIES 0x40 +#define SDHCI_TIMEOUT_CLK_MASK 0x0000003F +#define SDHCI_TIMEOUT_CLK_SHIFT 0 +#define SDHCI_TIMEOUT_CLK_UNIT 0x00000080 +#define SDHCI_CLOCK_BASE_MASK 0x00003F00 +#define SDHCI_CLOCK_BASE_SHIFT 8 +#define SDHCI_MAX_BLOCK_MASK 0x00030000 +#define SDHCI_MAX_BLOCK_SHIFT 16 +#define SDHCI_CAN_DO_ADMA2 0x00080000 +#define SDHCI_CAN_DO_HISPD 0x00200000 +#define SDHCI_CAN_DO_DMA 0x00400000 +#define SDHCI_CAN_DO_SUSPEND 0x00800000 +#define SDHCI_CAN_VDD_330 0x01000000 +#define SDHCI_CAN_VDD_300 0x02000000 +#define SDHCI_CAN_VDD_180 0x04000000 +#define SDHCI_CAN_DO_64BIT 0x10000000 + +#define SDHCI_MAX_CURRENT 0x48 + +#define SDHCI_SLOT_INT_STATUS 0xFC + +#define SDHCI_HOST_VERSION 0xFE +#define SDHCI_VENDOR_VER_MASK 0xFF00 +#define SDHCI_VENDOR_VER_SHIFT 8 +#define SDHCI_SPEC_VER_MASK 0x00FF +#define SDHCI_SPEC_VER_SHIFT 0