diff --git a/Makefile b/Makefile index 8753efeb..315b357e 100644 --- a/Makefile +++ b/Makefile @@ -59,6 +59,8 @@ LIB_C_FILES += rtemsbsd/local/miibus_if.c LIB_C_FILES += rtemsbsd/local/pcib_if.c LIB_C_FILES += rtemsbsd/local/pci_if.c LIB_C_FILES += rtemsbsd/local/usb_if.c +LIB_C_FILES += rtemsbsd/local/mmcbus_if.c +LIB_C_FILES += rtemsbsd/local/mmcbr_if.c LIB_C_FILES += rtemsbsd/rtems/ipsec_get_policylen.c LIB_C_FILES += rtemsbsd/rtems/rtems-bsd-assert.c LIB_C_FILES += rtemsbsd/rtems/rtems-bsd-arp-processor.c @@ -68,6 +70,7 @@ LIB_C_FILES += rtemsbsd/rtems/rtems-bsd-bus-dma-mbuf.c LIB_C_FILES += rtemsbsd/rtems/rtems-bsd-cam.c LIB_C_FILES += rtemsbsd/rtems/rtems-bsd-chunk.c LIB_C_FILES += rtemsbsd/rtems/rtems-bsd-conf.c +LIB_C_FILES += rtemsbsd/rtems/rtems-bsd-configintrhook.c LIB_C_FILES += rtemsbsd/rtems/rtems-bsd-delay.c LIB_C_FILES += rtemsbsd/rtems/rtems-bsd-get-ethernet-addr.c LIB_C_FILES += rtemsbsd/rtems/rtems-bsd-get-file.c @@ -129,6 +132,7 @@ LIB_C_FILES += rtemsbsd/sys/dev/usb/controller/ohci_lpc24xx.c LIB_C_FILES += rtemsbsd/sys/dev/usb/controller/ohci_lpc32xx.c LIB_C_FILES += rtemsbsd/sys/dev/smc/if_smc_nexus.c LIB_C_FILES += rtemsbsd/sys/dev/ffec/if_ffec_mcf548x.c +LIB_C_FILES += rtemsbsd/sys/dev/dw_mmc/dw_mmc.c LIB_C_FILES += rtemsbsd/sys/net/if_ppp.c LIB_C_FILES += rtemsbsd/sys/net/ppp_tty.c LIB_C_FILES += rtemsbsd/telnetd/check_passwd.c @@ -205,6 +209,9 @@ LIB_C_FILES += freebsd/sys/libkern/fls.c LIB_C_FILES += freebsd/sys/libkern/inet_ntoa.c LIB_C_FILES += freebsd/sys/libkern/random.c LIB_C_FILES += freebsd/sys/vm/uma_core.c +LIB_C_FILES += freebsd/sys/dev/mmc/mmc.c +LIB_C_FILES += freebsd/sys/dev/mmc/mmcsd.c +LIB_C_FILES += freebsd/sys/dev/sdhci/sdhci.c LIB_C_FILES += freebsd/sys/kern/sys_socket.c LIB_C_FILES += freebsd/sys/kern/uipc_syscalls.c LIB_C_FILES += freebsd/sys/net/bridgestp.c diff --git a/Makefile.todo b/Makefile.todo index e7f5a579..fe86dbd1 100644 --- a/Makefile.todo +++ b/Makefile.todo @@ -11,15 +11,10 @@ GENERATED += $(LOCAL_INC)/usb_if.h GENERATED += $(LOCAL_SRC)/usb_if.c GENERATED += $(LOCAL_INC)/bus_if.h GENERATED += $(LOCAL_SRC)/bus_if.c -GENERATED += $(LOCAL_INC)/linker_if.h -GENERATED += $(LOCAL_SRC)/linker_if.c GENERATED += $(LOCAL_INC)/device_if.h GENERATED += $(LOCAL_SRC)/device_if.c GENERATED += $(LOCAL_INC)/usbdevs_data.h GENERATED += $(LOCAL_INC)/usbdevs.h -GENERATED += $(LOCAL_INC)/vnode_if.h -GENERATED += $(LOCAL_INC)/vnode_if_newproto.h -GENERATED += $(LOCAL_INC)/vnode_if_typedef.h GENERATED += $(LOCAL_INC)/miibus_if.h GENERATED += $(LOCAL_SRC)/miibus_if.c GENERATED += $(LOCAL_INC)/miidevs.h @@ -27,12 +22,10 @@ GENERATED += $(LOCAL_INC)/pci_if.h GENERATED += $(LOCAL_SRC)/pci_if.c GENERATED += $(LOCAL_INC)/pcib_if.h GENERATED += $(LOCAL_SRC)/pcib_if.c -GENERATED += $(LOCAL_INC)/ofw_bus_if.h -GENERATED += $(LOCAL_SRC)/ofw_bus_if.c -GENERATED += $(LOCAL_INC)/ofw_if.h -GENERATED += $(LOCAL_SRC)/ofw_if.c -GENERATED += $(LOCAL_INC)/ata_if.h -GENERATED += $(LOCAL_SRC)/ata_if.c +GENERATED += $(LOCAL_INC)/mmcbr_if.h +GENERATED += $(LOCAL_SRC)/mmcbr_if.c +GENERATED += $(LOCAL_INC)/mmcbus_if.h +GENERATED += $(LOCAL_SRC)/mmcbus_if.c all: $(GENERATED) @@ -44,18 +37,6 @@ $(LOCAL_INC)/usbdevs_data.h: $(FREEBSD_SRC)/sys/dev/usb/usbdevs awk -f $(TOOLS)/usbdevs2h.awk $< -d mv usbdevs_data.h $@ -$(LOCAL_INC)/vnode_if.h: $(FREEBSD_SRC)/sys/kern/vnode_if.src - awk -f $(TOOLS)/vnode_if.awk $< -h - mv vnode_if.h $@ - -$(LOCAL_INC)/vnode_if_newproto.h: $(FREEBSD_SRC)/sys/kern/vnode_if.src - awk -f $(TOOLS)/vnode_if.awk $< -p - mv vnode_if_newproto.h $@ - -$(LOCAL_INC)/vnode_if_typedef.h: $(FREEBSD_SRC)/sys/kern/vnode_if.src - awk -f $(TOOLS)/vnode_if.awk $< -q - mv vnode_if_typedef.h $@ - $(LOCAL_INC)/usb_if.h: $(FREEBSD_SRC)/sys/dev/usb/usb_if.m awk -f $(TOOLS)/makeobjops.awk $< -h mv usb_if.h $@ @@ -72,14 +53,6 @@ $(LOCAL_SRC)/bus_if.c: $(FREEBSD_SRC)/sys/kern/bus_if.m awk -f $(TOOLS)/makeobjops.awk $< -c mv bus_if.c $@ -$(LOCAL_INC)/linker_if.h: $(FREEBSD_SRC)/sys/kern/linker_if.m - awk -f $(TOOLS)/makeobjops.awk $< -h - mv linker_if.h $@ - -$(LOCAL_SRC)/linker_if.c: $(FREEBSD_SRC)/sys/kern/linker_if.m - awk -f $(TOOLS)/makeobjops.awk $< -c - mv linker_if.c $@ - $(LOCAL_INC)/device_if.h: $(FREEBSD_SRC)/sys/kern/device_if.m awk -f $(TOOLS)/makeobjops.awk $< -h mv device_if.h $@ @@ -116,26 +89,18 @@ $(LOCAL_SRC)/pcib_if.c: $(FREEBSD_SRC)/sys/dev/pci/pcib_if.m awk -f $(TOOLS)/makeobjops.awk $< -c mv pcib_if.c $@ -$(LOCAL_INC)/ofw_bus_if.h: $(FREEBSD_SRC)/sys/dev/ofw/ofw_bus_if.m +$(LOCAL_INC)/mmcbus_if.h: $(FREEBSD_SRC)/sys/dev/mmc/mmcbus_if.m awk -f $(TOOLS)/makeobjops.awk $< -h - mv ofw_bus_if.h $@ + mv mmcbus_if.h $@ -$(LOCAL_SRC)/ofw_bus_if.c: $(FREEBSD_SRC)/sys/dev/ofw/ofw_bus_if.m +$(LOCAL_SRC)/mmcbus_if.c: $(FREEBSD_SRC)/sys/dev/mmc/mmcbus_if.m awk -f $(TOOLS)/makeobjops.awk $< -c - mv ofw_bus_if.c $@ + mv mmcbus_if.c $@ -$(LOCAL_INC)/ofw_if.h: $(FREEBSD_SRC)/sys/dev/ofw/ofw_if.m +$(LOCAL_INC)/mmcbr_if.h: $(FREEBSD_SRC)/sys/dev/mmc/mmcbr_if.m awk -f $(TOOLS)/makeobjops.awk $< -h - mv ofw_if.h $@ + mv mmcbr_if.h $@ -$(LOCAL_SRC)/ofw_if.c: $(FREEBSD_SRC)/sys/dev/ofw/ofw_if.m +$(LOCAL_SRC)/mmcbr_if.c: $(FREEBSD_SRC)/sys/dev/mmc/mmcbr_if.m awk -f $(TOOLS)/makeobjops.awk $< -c - mv ofw_if.c $@ - -$(LOCAL_INC)/ata_if.h: $(FREEBSD_SRC)/sys/dev/ata/ata_if.m - awk -f $(TOOLS)/makeobjops.awk $< -h - mv ata_if.h $@ - -$(LOCAL_SRC)/ata_if.c: $(FREEBSD_SRC)/sys/dev/ata/ata_if.m - awk -f $(TOOLS)/makeobjops.awk $< -c - mv ata_if.c $@ + mv mmcbr_if.c $@ diff --git a/freebsd-to-rtems.py b/freebsd-to-rtems.py index b91f7680..477b4edf 100755 --- a/freebsd-to-rtems.py +++ b/freebsd-to-rtems.py @@ -659,6 +659,8 @@ rtems.addRTEMSSourceFiles( 'local/pcib_if.c', 'local/pci_if.c', 'local/usb_if.c', + 'local/mmcbus_if.c', + 'local/mmcbr_if.c', 'rtems/ipsec_get_policylen.c', 'rtems/rtems-bsd-assert.c', 'rtems/rtems-bsd-arp-processor.c', @@ -668,6 +670,7 @@ rtems.addRTEMSSourceFiles( 'rtems/rtems-bsd-cam.c', 'rtems/rtems-bsd-chunk.c', 'rtems/rtems-bsd-conf.c', + 'rtems/rtems-bsd-configintrhook.c', 'rtems/rtems-bsd-delay.c', 'rtems/rtems-bsd-get-ethernet-addr.c', 'rtems/rtems-bsd-get-file.c', @@ -729,6 +732,7 @@ rtems.addRTEMSSourceFiles( 'sys/dev/usb/controller/ohci_lpc32xx.c', 'sys/dev/smc/if_smc_nexus.c', 'sys/dev/ffec/if_ffec_mcf548x.c', + 'sys/dev/dw_mmc/dw_mmc.c', 'sys/net/if_ppp.c', 'sys/net/ppp_tty.c', 'telnetd/check_passwd.c', @@ -918,6 +922,24 @@ base.addKernelSpaceSourceFiles( ] ) +mmc = Module('mmc') +mmc.addKernelSpaceHeaderFiles( + [ + 'sys/dev/mmc/bridge.h', + 'sys/dev/mmc/mmcbrvar.h', + 'sys/dev/mmc/mmcreg.h', + 'sys/dev/mmc/mmcvar.h', + 'sys/dev/sdhci/sdhci.h', + ] +) +mmc.addKernelSpaceSourceFiles( + [ + 'sys/dev/mmc/mmc.c', + 'sys/dev/mmc/mmcsd.c', + 'sys/dev/sdhci/sdhci.c', + ] +) + devUsb = Module('dev_usb') devUsb.addKernelSpaceHeaderFiles( [ @@ -2572,6 +2594,7 @@ mdnsresponder.addSourceFiles( # Register all the Module instances with the Module Manager mm.addModule(rtems) mm.addModule(base) +mm.addModule(mmc) mm.addModule(net) mm.addModule(netinet) mm.addModule(netinet6) diff --git a/freebsd/sys/dev/mmc/bridge.h b/freebsd/sys/dev/mmc/bridge.h new file mode 100644 index 00000000..bd61c15a --- /dev/null +++ b/freebsd/sys/dev/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$ + */ + +#ifndef DEV_MMC_BRIDGE_H +#define DEV_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 /* DEV_MMC_BRIDGE_H */ diff --git a/freebsd/sys/dev/mmc/mmc.c b/freebsd/sys/dev/mmc/mmc.c new file mode 100644 index 00000000..15175ba8 --- /dev/null +++ b/freebsd/sys/dev/mmc/mmc.c @@ -0,0 +1,1758 @@ +#include + +/*- + * 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __rtems__ +#include +#endif /* __rtems__ */ + +#include +#include +#include +#include +#include + +struct mmc_softc { + device_t dev; + struct mtx sc_mtx; + struct intr_config_hook config_intrhook; + device_t owner; + uint32_t last_rca; +#ifdef __rtems__ + struct cv req_done; +#endif /* __rtems__ */ +}; + +/* + * 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 */ + char card_id_string[64];/* Formatted CID info (serial, MFG, etc) */ +}; + +#define CMD_RETRIES 3 + +static 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_acquire_bus(device_t busdev, device_t dev); +static int mmc_attach(device_t dev); +static int mmc_child_location_str(device_t dev, device_t child, char *buf, + size_t buflen); +static int mmc_detach(device_t dev); +static int mmc_probe(device_t dev); +static int mmc_read_ivar(device_t bus, device_t child, int which, + uintptr_t *result); +static int mmc_release_bus(device_t busdev, device_t dev); +static int mmc_resume(device_t dev); +static int mmc_suspend(device_t dev); +static int mmc_wait_for_request(device_t brdev, device_t reqdev, + struct mmc_request *req); +static int mmc_write_ivar(device_t bus, device_t child, int which, + uintptr_t value); + +#define MMC_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) +#define MMC_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) +#define MMC_LOCK_INIT(_sc) \ + mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \ + "mmc", MTX_DEF) +#define MMC_LOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx); +#define MMC_ASSERT_LOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_OWNED); +#define MMC_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED); + +static int mmc_all_send_cid(struct mmc_softc *sc, uint32_t *rawcid); +static void mmc_app_decode_scr(uint32_t *raw_scr, struct mmc_scr *scr); +static void mmc_app_decode_sd_status(uint32_t *raw_sd_status, + struct mmc_sd_status *sd_status); +static int mmc_app_sd_status(struct mmc_softc *sc, uint16_t rca, + uint32_t *rawsdstatus); +static int mmc_app_send_scr(struct mmc_softc *sc, uint16_t rca, + uint32_t *rawscr); +static int mmc_calculate_clock(struct mmc_softc *sc); +static void mmc_decode_cid_mmc(uint32_t *raw_cid, struct mmc_cid *cid); +static void mmc_decode_cid_sd(uint32_t *raw_cid, struct mmc_cid *cid); +static void mmc_decode_csd_mmc(uint32_t *raw_csd, struct mmc_csd *csd); +static void mmc_decode_csd_sd(uint32_t *raw_csd, struct mmc_csd *csd); +static void mmc_delayed_attach(void *xsc); +static int mmc_delete_cards(struct mmc_softc *sc); +static void mmc_discover_cards(struct mmc_softc *sc); +static void mmc_format_card_id_string(struct mmc_ivars *ivar); +static void mmc_go_discovery(struct mmc_softc *sc); +static uint32_t mmc_get_bits(uint32_t *bits, int bit_len, int start, + int size); +static int mmc_highest_voltage(uint32_t ocr); +static void mmc_idle_cards(struct mmc_softc *sc); +static void mmc_ms_delay(int ms); +static void mmc_log_card(device_t dev, struct mmc_ivars *ivar, int newcard); +static void mmc_power_down(struct mmc_softc *sc); +static void mmc_power_up(struct mmc_softc *sc); +static void mmc_rescan_cards(struct mmc_softc *sc); +static void mmc_scan(struct mmc_softc *sc); +static int mmc_sd_switch(struct mmc_softc *sc, uint8_t mode, uint8_t grp, + uint8_t value, uint8_t *res); +static int mmc_select_card(struct mmc_softc *sc, uint16_t rca); +static uint32_t mmc_select_vdd(struct mmc_softc *sc, uint32_t ocr); +static int mmc_send_app_op_cond(struct mmc_softc *sc, uint32_t ocr, + uint32_t *rocr); +static int mmc_send_csd(struct mmc_softc *sc, uint16_t rca, uint32_t *rawcsd); +static int mmc_send_ext_csd(struct mmc_softc *sc, uint8_t *rawextcsd); +static int mmc_send_if_cond(struct mmc_softc *sc, uint8_t vhs); +static int mmc_send_op_cond(struct mmc_softc *sc, uint32_t ocr, + uint32_t *rocr); +static int mmc_send_relative_addr(struct mmc_softc *sc, uint32_t *resp); +static int mmc_send_status(struct mmc_softc *sc, uint16_t rca, + uint32_t *status); +static int mmc_set_blocklen(struct mmc_softc *sc, uint32_t len); +static int mmc_set_card_bus_width(struct mmc_softc *sc, uint16_t rca, + int width); +static int mmc_set_relative_addr(struct mmc_softc *sc, uint16_t resp); +static int mmc_set_timing(struct mmc_softc *sc, int timing); +static int mmc_switch(struct mmc_softc *sc, uint8_t set, uint8_t index, + uint8_t value); +static int mmc_test_bus_width(struct mmc_softc *sc); +static int mmc_wait_for_app_cmd(struct mmc_softc *sc, uint32_t rca, + struct mmc_command *cmd, int retries); +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_wait_for_req(struct mmc_softc *sc, struct mmc_request *req); +static void mmc_wakeup(struct mmc_request *req); + +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); +#ifdef __rtems__ + cv_init(&sc->req_done, "MMC request done"); +#endif /* __rtems__ */ + + /* 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); +#ifdef __rtems__ + cv_destroy(&sc->req_done); +#endif /* __rtems__ */ + 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 serialize 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; +#ifdef __rtems__ + cv_broadcast(&sc->req_done); +#endif /* __rtems__ */ + MMC_UNLOCK(sc); +#ifndef __rtems__ + wakeup(req); +#endif /* __rtems__ */ +} + +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) { + printf(" data %d\n", (int)req->cmd->data->len); + } else + printf("\n"); + } + MMCBR_REQUEST(device_get_parent(sc->dev), sc->dev, req); + MMC_LOCK(sc); + while ((req->flags & MMC_REQ_DONE) == 0) +#ifndef __rtems__ + msleep(req, &sc->sc_mtx, 0, "mmcreq", 0); +#else /* __rtems__ */ + cv_wait(&sc->req_done, &sc->sc_mtx); +#endif /* __rtems__ */ + 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 < 1000; 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 < 1000; i++) { + err = mmc_wait_for_cmd(sc, &cmd, CMD_RETRIES); + if (err != MMC_ERR_NONE) + break; + if ((cmd.resp[0] & MMC_OCR_CARD_BUSY) || + (ocr & MMC_OCR_VOLTAGE) == 0) + break; + err = MMC_ERR_TIMEOUT; + mmc_ms_delay(10); + } + if (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_CLR_CARD_DETECT; + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + cmd.arg = SD_CLR_CARD_DETECT; + err = mmc_wait_for_app_cmd(sc, rca, &cmd, CMD_RETRIES); + if (err != 0) + return (err); + 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 & ((1llu << 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 void +mmc_format_card_id_string(struct mmc_ivars *ivar) +{ + char oidstr[8]; + uint8_t c1; + uint8_t c2; + + /* + * Format a card ID string for use by the mmcsd driver, it's what + * appears between the <> in the following: + * mmcsd0: 968MB at mmc0 + * 22.5MHz/4bit/128-block + * + * The card_id_string in mmc_ivars is currently allocated as 64 bytes, + * and our max formatted length is currently 55 bytes if every field + * contains the largest value. + * + * Sometimes the oid is two printable ascii chars; when it's not, + * format it as 0xnnnn instead. + */ + c1 = (ivar->cid.oid >> 8) & 0x0ff; + c2 = ivar->cid.oid & 0x0ff; + if (c1 > 0x1f && c1 < 0x7f && c2 > 0x1f && c2 < 0x7f) + snprintf(oidstr, sizeof(oidstr), "%c%c", c1, c2); + else + snprintf(oidstr, sizeof(oidstr), "0x%04x", ivar->cid.oid); + snprintf(ivar->card_id_string, sizeof(ivar->card_id_string), + "%s%s %s %d.%d SN %u MFG %02d/%04d by %d %s", + ivar->mode == mode_sd ? "SD" : "MMC", ivar->high_cap ? "HC" : "", + ivar->cid.pnm, ivar->cid.prv >> 4, ivar->cid.prv & 0x0f, + ivar->cid.psn, ivar->cid.mdt_month, ivar->cid.mdt_year, + ivar->cid.mid, oidstr); +} + +static const int exp[8] = { + 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000 +}; + +static const int mant[16] = { + 0, 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) { + printf("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 *rawcsd) +{ + 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(rawcsd, 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 int +mmc_send_status(struct mmc_softc *sc, uint16_t rca, uint32_t *status) +{ + struct mmc_command cmd; + int err; + + cmd.opcode = MMC_SEND_STATUS; + cmd.arg = rca << 16; + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + cmd.data = NULL; + err = mmc_wait_for_cmd(sc, &cmd, 0); + *status = cmd.resp[0]; + return (err); +} + +static int +mmc_set_blocklen(struct mmc_softc *sc, uint32_t len) +{ + struct mmc_command cmd; + int err; + + cmd.opcode = MMC_SET_BLOCKLEN; + cmd.arg = len; + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + cmd.data = NULL; + err = mmc_wait_for_cmd(sc, &cmd, 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\n", ivar->card_id_string); + 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], resp, sec_count, status; + 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; + } + } + free(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 = malloc(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); + if (bootverbose || mmc_debug) + device_printf(sc->dev, + "%sard detected (CSD %08x%08x%08x%08x)\n", + newcard ? "New c" : "C", ivar->raw_csd[0], + ivar->raw_csd[1], ivar->raw_csd[2], + ivar->raw_csd[3]); + 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; + + err = mmc_send_status(sc, ivar->rca, &status); + if (err != MMC_ERR_NONE) { + device_printf(sc->dev, + "Error reading card status %d\n", err); + break; + } + if ((status & R1_CARD_IS_LOCKED) != 0) { + device_printf(sc->dev, + "Card is password protected, skipping.\n"); + break; + } + + /* 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; + + /* + * Some cards that report maximum I/O block sizes + * greater than 512 require the block length to be + * set to 512, even though that is supposed to be + * the default. Example: + * + * Transcend 2GB SDSC card, CID: + * mid=0x1b oid=0x534d pnm="00000" prv=1.0 mdt=00.2000 + */ + if (ivar->csd.read_bl_len != MMC_SECTOR_SIZE || + ivar->csd.write_bl_len != MMC_SECTOR_SIZE) + mmc_set_blocklen(sc, MMC_SECTOR_SIZE); + + mmc_format_card_id_string(ivar); + + 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); + if (bootverbose || mmc_debug) + device_printf(sc->dev, + "%sard detected (CSD %08x%08x%08x%08x)\n", + newcard ? "New c" : "C", ivar->raw_csd[0], + ivar->raw_csd[1], ivar->raw_csd[2], + ivar->raw_csd[3]); + + 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; + + err = mmc_send_status(sc, ivar->rca, &status); + if (err != MMC_ERR_NONE) { + device_printf(sc->dev, + "Error reading card status %d\n", err); + break; + } + if ((status & R1_CARD_IS_LOCKED) != 0) { + device_printf(sc->dev, + "Card is password protected, skipping.\n"); + break; + } + + /* 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; + } + + /* + * Some cards that report maximum I/O block sizes greater + * than 512 require the block length to be set to 512, even + * though that is supposed to be the default. Example: + * + * Transcend 2GB SDSC card, CID: + * mid=0x1b oid=0x534d pnm="00000" prv=1.0 mdt=00.2000 + */ + if (ivar->csd.read_bl_len != MMC_SECTOR_SIZE || + ivar->csd.write_bl_len != MMC_SECTOR_SIZE) + mmc_set_blocklen(sc, MMC_SECTOR_SIZE); + + mmc_format_card_id_string(ivar); + + 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]); + free(ivar, M_DEVBUF); + } + } + free(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]); + free(ivar, M_DEVBUF); + } + free(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, 0, &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); + free(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: + *result = ivar->csd.dsr_imp; + break; + case MMC_IVAR_MEDIA_SIZE: + *result = ivar->sec_count; + break; + case MMC_IVAR_RCA: + *result = ivar->rca; + break; + case MMC_IVAR_SECTOR_SIZE: + *result = MMC_SECTOR_SIZE; + break; + case MMC_IVAR_TRAN_SPEED: + *result = mmcbr_get_clock(bus); + break; + case MMC_IVAR_READ_ONLY: + *result = ivar->read_only; + break; + case MMC_IVAR_HIGH_CAP: + *result = ivar->high_cap; + break; + case MMC_IVAR_CARD_TYPE: + *result = ivar->mode; + break; + case MMC_IVAR_BUS_WIDTH: + *result = ivar->bus_width; + break; + case MMC_IVAR_ERASE_SECTOR: + *result = ivar->erase_sector; + break; + case MMC_IVAR_MAX_DATA: + *result = mmcbr_get_max_data(bus); + break; + case MMC_IVAR_CARD_ID_STRING: + *(char **)result = ivar->card_id_string; + 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 int +mmc_child_location_str(device_t dev, device_t child, char *buf, + size_t buflen) +{ + + snprintf(buf, buflen, "rca=0x%04x", mmc_get_rca(child)); + return (0); +} + +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), + DEVMETHOD(bus_child_location_str, mmc_child_location_str), + + /* 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), + + DEVMETHOD_END +}; + +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, dw_mmc, mmc_driver, mmc_devclass, NULL, NULL); +DRIVER_MODULE(mmc, sdhci, mmc_driver, mmc_devclass, NULL, NULL); diff --git a/freebsd/sys/dev/mmc/mmcbrvar.h b/freebsd/sys/dev/mmc/mmcbrvar.h new file mode 100644 index 00000000..1f0a5714 --- /dev/null +++ b/freebsd/sys/dev/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$" + */ + +#ifndef DEV_MMC_MMCBRVAR_H +#define DEV_MMC_MMCBRVAR_H + +#include +#include +#include + +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 +}; + +/* + * 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 /* DEV_MMC_MMCBRVAR_H */ diff --git a/freebsd/sys/dev/mmc/mmcreg.h b/freebsd/sys/dev/mmc/mmcreg.h new file mode 100644 index 00000000..3d2b5691 --- /dev/null +++ b/freebsd/sys/dev/mmc/mmcreg.h @@ -0,0 +1,443 @@ +/*- + * 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$ + */ + +#ifndef DEV_MMC_MMCREG_H +#define DEV_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_CLR_CARD_DETECT 0 +#define SD_SET_CARD_DETECT 1 + +#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 /* DEV_MMCREG_H */ diff --git a/freebsd/sys/dev/mmc/mmcsd.c b/freebsd/sys/dev/mmc/mmcsd.c new file mode 100644 index 00000000..42c45cf7 --- /dev/null +++ b/freebsd/sys/dev/mmc/mmcsd.c @@ -0,0 +1,801 @@ +#include + +/*- + * 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#ifdef __rtems__ +#include +#include +#include +#include +#include +#endif /* __rtems__ */ + +#if __FreeBSD_version < 800002 +#define kproc_create kthread_create +#define kproc_exit kthread_exit +#endif + +struct mmcsd_softc { + device_t dev; + struct mtx sc_mtx; +#ifndef __rtems__ + struct disk *disk; + struct proc *p; + struct bio_queue_head bio_queue; + daddr_t eblock, eend; /* Range remaining after the last erase. */ + int running; + int suspend; +#endif /* __rtems__ */ +}; + +/* bus entry points */ +static int mmcsd_attach(device_t dev); +static int mmcsd_detach(device_t dev); +static int mmcsd_probe(device_t dev); + +#ifndef __rtems__ +/* disk routines */ +static int mmcsd_close(struct disk *dp); +static int mmcsd_dump(void *arg, void *virtual, vm_offset_t physical, + off_t offset, size_t length); +static int mmcsd_open(struct disk *dp); +static void mmcsd_strategy(struct bio *bp); +static void mmcsd_task(void *arg); +#endif /* __rtems__ */ + +static int mmcsd_bus_bit_width(device_t dev); +#ifndef __rtems__ +static daddr_t mmcsd_delete(struct mmcsd_softc *sc, struct bio *bp); +static daddr_t mmcsd_rw(struct mmcsd_softc *sc, struct bio *bp); +#endif /* __rtems__ */ + +#define MMCSD_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) +#define MMCSD_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) +#define MMCSD_LOCK_INIT(_sc) \ + mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \ + "mmcsd", MTX_DEF) +#define MMCSD_LOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx); +#define MMCSD_ASSERT_LOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_OWNED); +#define MMCSD_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED); + +static int +mmcsd_probe(device_t dev) +{ + + device_quiet(dev); + device_set_desc(dev, "MMC/SD Memory Card"); + return (0); +} + +#ifdef __rtems__ +static rtems_status_code +rtems_bsd_mmcsd_set_block_size(struct mmcsd_softc *self, uint32_t block_size) +{ + rtems_status_code status_code = RTEMS_SUCCESSFUL; + device_t dev = self->dev; + struct mmc_command cmd; + struct mmc_request req; + + memset(&req, 0, sizeof(req)); + memset(&cmd, 0, sizeof(cmd)); + + req.cmd = &cmd; + cmd.opcode = MMC_SET_BLOCKLEN; + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + cmd.arg = block_size; + MMCBUS_WAIT_FOR_REQUEST(device_get_parent(dev), dev, + &req); + if (req.cmd->error != MMC_ERR_NONE) { + status_code = RTEMS_IO_ERROR; + } + + return status_code; +} + +static int +rtems_bsd_mmcsd_disk_read_write(struct mmcsd_softc *self, rtems_blkdev_request *blkreq) +{ + rtems_status_code status_code = RTEMS_SUCCESSFUL; + device_t dev = self->dev; + int shift = mmc_get_high_cap(dev) ? 0 : 9; + int rca = mmc_get_rca(dev); + uint32_t buffer_count = blkreq->bufnum; + uint32_t transfer_bytes = blkreq->bufs[0].length; + uint32_t block_count = transfer_bytes / MMC_SECTOR_SIZE; + uint32_t opcode; + uint32_t data_flags; + uint32_t i; + + if (blkreq->req == RTEMS_BLKDEV_REQ_WRITE) { + if (block_count > 1) { + opcode = MMC_WRITE_MULTIPLE_BLOCK; + } else { + opcode = MMC_WRITE_BLOCK; + } + + data_flags = MMC_DATA_WRITE; + } else { + BSD_ASSERT(blkreq->req == RTEMS_BLKDEV_REQ_READ); + + if (block_count > 1) { + opcode = MMC_READ_MULTIPLE_BLOCK; + } else { + opcode = MMC_READ_SINGLE_BLOCK; + } + + data_flags = MMC_DATA_READ; + } + + MMCSD_LOCK(self); + + for (i = 0; i < buffer_count; ++i) { + rtems_blkdev_sg_buffer *sg = &blkreq->bufs [i]; + struct mmc_request req; + struct mmc_command cmd; + struct mmc_command stop; + struct mmc_data data; + rtems_interval timeout; + + memset(&req, 0, sizeof(req)); + memset(&cmd, 0, sizeof(cmd)); + memset(&stop, 0, sizeof(stop)); + + req.cmd = &cmd; + + cmd.opcode = opcode; + cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + cmd.data = &data; + cmd.arg = sg->block << shift; + + if (block_count > 1) { + data_flags |= MMC_DATA_MULTI; + stop.opcode = MMC_STOP_TRANSMISSION; + stop.flags = MMC_RSP_R1B | MMC_CMD_AC; + req.stop = &stop; + } + + data.flags = data_flags;; + data.data = sg->buffer; + data.mrq = &req; + data.len = transfer_bytes; + + MMCBUS_WAIT_FOR_REQUEST(device_get_parent(dev), dev, + &req); + if (req.cmd->error != MMC_ERR_NONE) { + status_code = RTEMS_IO_ERROR; + goto error; + } + + timeout = rtems_clock_tick_later_usec(250000); + while (1) { + struct mmc_request req2; + struct mmc_command cmd2; + uint32_t status; + + memset(&req2, 0, sizeof(req2)); + memset(&cmd2, 0, sizeof(cmd2)); + + req2.cmd = &cmd2; + + cmd2.opcode = MMC_SEND_STATUS; + cmd2.arg = rca << 16; + cmd2.flags = MMC_RSP_R1 | MMC_CMD_AC; + + MMCBUS_WAIT_FOR_REQUEST(device_get_parent(dev), dev, + &req2); + if (req2.cmd->error != MMC_ERR_NONE) { + status_code = RTEMS_IO_ERROR; + goto error; + } + + status = cmd2.resp[0]; + if ((status & R1_READY_FOR_DATA) != 0 + && R1_CURRENT_STATE(status) != R1_STATE_PRG) { + break; + } + + if (!rtems_clock_tick_before(timeout)) { + status_code = RTEMS_IO_ERROR; + goto error; + } + } + } + +error: + + MMCSD_UNLOCK(self); + + rtems_blkdev_request_done(blkreq, status_code); + + return 0; +} + +static int +rtems_bsd_mmcsd_disk_ioctl(rtems_disk_device *dd, uint32_t req, void *arg) +{ + struct mmcsd_softc *self = rtems_disk_get_driver_data(dd); + + if (req == RTEMS_BLKIO_REQUEST) { + rtems_blkdev_request *blkreq = arg; + + return rtems_bsd_mmcsd_disk_read_write(self, blkreq); + } else if (req == RTEMS_BLKIO_CAPABILITIES) { + *(uint32_t *) arg = RTEMS_BLKDEV_CAP_MULTISECTOR_CONT; + return 0; + } else { + return rtems_blkdev_ioctl(dd, req, arg); + } +} + +static rtems_status_code +rtems_bsd_mmcsd_attach_worker(rtems_media_state state, const char *src, char **dest, void *arg) +{ + rtems_status_code status_code = RTEMS_SUCCESSFUL; + struct mmcsd_softc *self = arg; + char *disk = NULL; + + if (state == RTEMS_MEDIA_STATE_READY) { + device_t dev = self->dev; + uint32_t block_count = mmc_get_media_size(dev); + uint32_t block_size = MMC_SECTOR_SIZE; + + disk = rtems_media_create_path("/dev", src, device_get_unit(dev)); + if (disk == NULL) { + printf("OOPS: create path failed\n"); + goto error; + } + + MMCBUS_ACQUIRE_BUS(device_get_parent(dev), dev); + + status_code = rtems_bsd_mmcsd_set_block_size(self, block_size); + if (status_code != RTEMS_SUCCESSFUL) { + printf("OOPS: set block size failed\n"); + goto error; + } + + status_code = rtems_blkdev_create(disk, block_size, + block_count, rtems_bsd_mmcsd_disk_ioctl, self); + if (status_code != RTEMS_SUCCESSFUL) { + goto error; + } + + *dest = strdup(disk, M_RTEMS_HEAP); + } + + return RTEMS_SUCCESSFUL; + +error: + free(disk, M_RTEMS_HEAP); + + return RTEMS_IO_ERROR; +} +#endif /* __rtems__ */ +static int +mmcsd_attach(device_t dev) +{ + struct mmcsd_softc *sc; +#ifndef __rtems__ + struct disk *d; +#endif /* __rtems__ */ + intmax_t mb; + uint32_t speed; + uint32_t maxblocks; + char unit; + + sc = device_get_softc(dev); + sc->dev = dev; + MMCSD_LOCK_INIT(sc); + +#ifndef __rtems__ + d = sc->disk = disk_alloc(); + d->d_open = mmcsd_open; + d->d_close = mmcsd_close; + d->d_strategy = mmcsd_strategy; + d->d_dump = mmcsd_dump; + d->d_name = "mmcsd"; + d->d_drv1 = sc; + d->d_maxsize = 4*1024*1024; /* Maximum defined SD card AU size. */ + d->d_sectorsize = mmc_get_sector_size(dev); + d->d_mediasize = (off_t)mmc_get_media_size(dev) * d->d_sectorsize; + d->d_stripeoffset = 0; + d->d_stripesize = mmc_get_erase_sector(dev) * d->d_sectorsize; + d->d_unit = device_get_unit(dev); + d->d_flags = DISKFLAG_CANDELETE; + /* + * 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 = d->d_mediasize >> 20; /* 1MiB == 1 << 20 */ +#else /* __rtems__ */ + mb = mmc_get_media_size(dev); + mb *= mmc_get_sector_size(dev); + mb >>= 20; +#endif /* __rtems__ */ + unit = 'M'; + if (mb >= 10240) { /* 1GiB = 1024 MiB */ + unit = 'G'; + mb /= 1024; + } + /* + * Report the clock speed of the underlying hardware, which might be + * different than what the card reports due to hardware limitations. + * Report how many blocks the hardware transfers at once, but clip the + * number to MAXPHYS since the system won't initiate larger transfers. + */ + speed = mmcbr_get_clock(device_get_parent(dev)); + maxblocks = mmc_get_max_data(dev); + if (maxblocks > MAXPHYS) + maxblocks = MAXPHYS; + device_printf(dev, "%ju%cB <%s>%s at %s %d.%01dMHz/%dbit/%d-block\n", + mb, unit, mmc_get_card_id_string(dev), + mmc_get_read_only(dev) ? " (read-only)" : "", + device_get_nameunit(device_get_parent(dev)), + speed / 1000000, (speed / 100000) % 10, + mmcsd_bus_bit_width(dev), maxblocks); +#ifndef __rtems__ + disk_create(d, DISK_VERSION); + bioq_init(&sc->bio_queue); + + sc->running = 1; + sc->suspend = 0; + sc->eblock = sc->eend = 0; + kproc_create(&mmcsd_task, sc, &sc->p, 0, 0, "task: mmc/sd card"); +#else /* __rtems__ */ + rtems_status_code status_code = rtems_media_server_disk_attach( + device_get_name(dev), + rtems_bsd_mmcsd_attach_worker, + sc + ); + BSD_ASSERT(status_code == RTEMS_SUCCESSFUL); +#endif /* __rtems__ */ + + return (0); +} + +static int +mmcsd_detach(device_t dev) +{ + struct mmcsd_softc *sc = device_get_softc(dev); + +#ifndef __rtems__ + 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) + msleep(sc, &sc->sc_mtx, 0, "detach", 0); + } + MMCSD_UNLOCK(sc); + + /* Flush the request queue. */ + bioq_flush(&sc->bio_queue, NULL, ENXIO); + /* kill disk */ + disk_destroy(sc->disk); +#else /* __rtems__ */ + BSD_PANIC("FIXME"); +#endif /* __rtems__ */ + + MMCSD_LOCK_DESTROY(sc); + + return (0); +} + +static int +mmcsd_suspend(device_t dev) +{ +#ifndef __rtems__ + 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) + msleep(sc, &sc->sc_mtx, 0, "detach", 0); + } + MMCSD_UNLOCK(sc); +#else /* __rtems__ */ + BSD_PANIC("FIXME"); +#endif /* __rtems__ */ + return (0); +} + +static int +mmcsd_resume(device_t dev) +{ +#ifndef __rtems__ + struct mmcsd_softc *sc = device_get_softc(dev); + + MMCSD_LOCK(sc); + sc->suspend = 0; + if (sc->running <= 0) { + sc->running = 1; + MMCSD_UNLOCK(sc); + kproc_create(&mmcsd_task, sc, &sc->p, 0, 0, "task: mmc/sd card"); + } else + MMCSD_UNLOCK(sc); +#else /* __rtems__ */ + BSD_PANIC("FIXME"); +#endif /* __rtems__ */ + return (0); +} + +#ifndef __rtems__ +static int +mmcsd_open(struct disk *dp) +{ + + return (0); +} + +static int +mmcsd_close(struct disk *dp) +{ + + return (0); +} + +static void +mmcsd_strategy(struct bio *bp) +{ + struct mmcsd_softc *sc; + + sc = (struct mmcsd_softc *)bp->bio_disk->d_drv1; + MMCSD_LOCK(sc); + if (sc->running > 0 || sc->suspend > 0) { + bioq_disksort(&sc->bio_queue, bp); + MMCSD_UNLOCK(sc); + wakeup(sc); + } else { + MMCSD_UNLOCK(sc); + biofinish(bp, NULL, ENXIO); + } +} + +static daddr_t +mmcsd_rw(struct mmcsd_softc *sc, struct bio *bp) +{ + 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_sectorsize; + + block = bp->bio_pblkno; + end = bp->bio_pblkno + (bp->bio_bcount / sz); + while (block < end) { + char *vaddr = bp->bio_data + + (block - bp->bio_pblkno) * 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->bio_cmd == BIO_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->bio_cmd == BIO_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; + } +// printf("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 *bp) +{ + daddr_t block, end, start, stop; + struct mmc_command cmd; + struct mmc_request req; + device_t dev = sc->dev; + int sz = sc->disk->d_sectorsize; + int erase_sector; + + block = bp->bio_pblkno; + end = bp->bio_pblkno + (bp->bio_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) { + printf("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) { + printf("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) { + printf("erase err3 %d\n", req.cmd->error); + return (block); + } + /* Store one of remaining parts for the next call. */ + if (bp->bio_pblkno >= 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(void *arg, void *virtual, vm_offset_t physical, + off_t offset, size_t length) +{ + struct disk *disk = arg; + struct mmcsd_softc *sc = (struct mmcsd_softc *)disk->d_drv1; + device_t dev = sc->dev; + struct bio bp; + daddr_t block, end; + + /* length zero is special and really means flush buffers to media */ + if (!length) + return (0); + + bzero(&bp, sizeof(struct bio)); + bp.bio_disk = disk; + bp.bio_pblkno = offset / 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_sectorsize; + 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); +} + +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) + msleep(sc, &sc->sc_mtx, PRIBIO, "jobqueue", 0); + } while (bp == NULL); + MMCSD_UNLOCK(sc); + if (bp->bio_cmd != BIO_READ && mmc_get_read_only(dev)) { + bp->bio_error = EROFS; + bp->bio_resid = bp->bio_bcount; + bp->bio_flags |= BIO_ERROR; + biodone(bp); + continue; + } + MMCBUS_ACQUIRE_BUS(device_get_parent(dev), dev); + sz = sc->disk->d_sectorsize; + block = bp->bio_pblkno; + end = bp->bio_pblkno + (bp->bio_bcount / sz); + if (bp->bio_cmd == BIO_READ || bp->bio_cmd == BIO_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_cmd == BIO_DELETE) { + block = mmcsd_delete(sc, bp); + } + MMCBUS_RELEASE_BUS(device_get_parent(dev), dev); + if (block < end) { + bp->bio_error = EIO; + bp->bio_resid = (end - block) * sz; + bp->bio_flags |= BIO_ERROR; + } + biodone(bp); + } +out: + /* tell parent we're done */ + sc->running = -1; + MMCSD_UNLOCK(sc); + wakeup(sc); + + kproc_exit(0); +} +#endif /* __rtems__ */ + +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), + DEVMETHOD_END +}; + +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, NULL, NULL); diff --git a/freebsd/sys/dev/mmc/mmcvar.h b/freebsd/sys/dev/mmc/mmcvar.h new file mode 100644 index 00000000..99b8a6a5 --- /dev/null +++ b/freebsd/sys/dev/mmc/mmcvar.h @@ -0,0 +1,102 @@ +/*- + * 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$" + */ + +#ifndef DEV_MMC_MMCVAR_H +#define DEV_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_CARD_ID_STRING +}; + +/* + * 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) +#ifndef __rtems__ +MMC_ACCESSOR(media_size, MEDIA_SIZE, off_t) +#else /* __rtems__ */ +/* + * The instance variable value storage is limited by the uintptr_t type. Since + * off_t has more bits than uintptr_t on most RTEMS targets, we need this hack. + */ +MMC_ACCESSOR(media_size, MEDIA_SIZE, uintptr_t) +#endif /* __rtems__ */ +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) +MMC_ACCESSOR(card_id_string, CARD_ID_STRING, const char *) + +#endif /* DEV_MMC_MMCVAR_H */ diff --git a/freebsd/sys/dev/sdhci/sdhci.c b/freebsd/sys/dev/sdhci/sdhci.c new file mode 100644 index 00000000..86c75b27 --- /dev/null +++ b/freebsd/sys/dev/sdhci/sdhci.c @@ -0,0 +1,1907 @@ +#include + +/*- + * 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#ifndef __rtems__ +#include +#else /* __rtems__ */ +#include +#endif /* __rtems__ */ +#ifdef __rtems__ +#include +#ifdef LIBBSP_POWERPC_QORIQ_BSP_H + #include + #include + #include + #define RTEMS_BSD_SDHCI_QUIRK_NO_BUSY_IRQ + #define RTEMS_BSD_SDHCI_QUIRK_32_BIT_REGS +#endif +#endif /* __rtems__ */ + +#ifndef __rtems__ +#define DMA_BLOCK_SIZE 4096 +#else /* __rtems__ */ +#define DMA_BLOCK_SIZE 512 +#endif /* __rtems__ */ +#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) + +#ifndef __rtems__ +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 } +}; +#endif /* __rtems__ */ + +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 */ +#ifndef __rtems__ + struct resource *mem_res; /* Memory resource */ + int mem_rid; +#else /* __rtems__ */ + struct resource mem_res [1]; /* Memory resource */ +#endif /* __rtems__ */ +#ifndef __rtems__ + 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 */ +#endif /* __rtems__ */ + 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 mtx mtx; /* Slot mutex */ +}; + +struct sdhci_softc { + device_t dev; /* Controller device */ + u_int quirks; /* Chip specific quirks */ +#ifndef __rtems__ + struct resource *irq_res; /* IRQ resource */ + int irq_rid; + void *intrhand; /* Interrupt handle */ +#endif /* __rtems__ */ + + 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); +#ifndef __rtems__ + return bus_read_1(slot->mem_res, off); +#else /* __rtems__ */ + /* FIXME */ + bus_size_t aligned_off = off & ~0x3; + bus_size_t shift = (off & 0x3) * 8; + uint32_t val = bus_read_4(slot->mem_res, aligned_off); + return val >> shift; +#endif /* __rtems__ */ +} + +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); +#ifndef __rtems__ + bus_write_1(slot->mem_res, off, val); +#else /* __rtems__ */ + /* FIXME */ + if (off != SDHCI_POWER_CONTROL) { + bus_size_t aligned_off = off & ~0x3; + bus_size_t shift = (off & 0x3) * 8; + uint32_t reg = bus_read_4(slot->mem_res, aligned_off); + reg &= ~(0xff << shift); + reg |= val << shift; + bus_write_4(slot->mem_res, aligned_off, reg); + } +#endif /* __rtems__ */ +} + +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); +#ifndef __rtems__ + return bus_read_2(slot->mem_res, off); +#else /* __rtems__ */ + /* FIXME */ + bus_size_t aligned_off = off & ~0x3; + bus_size_t shift = (off & 0x2) * 8; + uint32_t val = bus_read_4(slot->mem_res, aligned_off); + return val >> shift; +#endif /* __rtems__ */ +} + +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); +#ifndef __rtems__ + bus_write_2(slot->mem_res, off, val); +#else /* __rtems__ */ + /* FIXME */ + bus_size_t aligned_off = off & ~0x3; + bus_size_t shift = (off & 0x2) * 8; + uint32_t reg = bus_read_4(slot->mem_res, aligned_off); + reg &= ~(0xffff << shift); + reg |= val << shift; + bus_write_4(slot->mem_res, aligned_off, reg); +#endif /* __rtems__ */ +} + +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); +#if defined(__rtems__) && defined(LIBBSP_POWERPC_QORIQ_BSP_H) + uint32_t val = bus_read_4(slot->mem_res, off); + if (off == SDHCI_BUFFER) { + /* The DATPORT register use little-endian order */ + val = CPU_swap_u32(val); + } + return val; +#else /* __rtems__ */ + return bus_read_4(slot->mem_res, off); +#endif /* __rtems__ */ +} + +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); +#if defined(__rtems__) && defined(LIBBSP_POWERPC_QORIQ_BSP_H) + if (off == SDHCI_BUFFER) { + /* The DATPORT register use little-endian order */ + val = CPU_swap_u32(val); + } +#endif /* __rtems__ */ + 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) mtx_lock(&(_slot)->mtx) +#define SDHCI_UNLOCK(_slot) mtx_unlock(&(_slot)->mtx) +#define SDHCI_LOCK_INIT(_slot) \ + mtx_init(&_slot->mtx, "SD slot mtx", "sdhci", MTX_DEF) +#define SDHCI_LOCK_DESTROY(_slot) mtx_destroy(&_slot->mtx); +#define SDHCI_ASSERT_LOCKED(_slot) mtx_assert(&_slot->mtx, MA_OWNED); +#define SDHCI_ASSERT_UNLOCKED(_slot) mtx_assert(&_slot->mtx, MA_NOTOWNED); + +static int +slot_printf(struct sdhci_slot *slot, const char * fmt, ...) +{ + va_list ap; + int retval; + + retval = printf("%s-slot%d: ", + device_get_nameunit(slot->sc->dev), slot->num); + + va_start(ap, fmt); + retval += vprintf(fmt, ap); + va_end(ap); + return (retval); +} + +#ifndef __rtems__ +static void +sdhci_getaddr(void *arg, bus_dma_segment_t *segs, int nsegs, int error) +{ + if (error != 0) { + printf("getaddr: error %d\n", error); + return; + } + *(bus_addr_t *)arg = segs[0].ds_addr; +} +#endif /* __rtems__ */ + +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); + } + +#ifdef __rtems__ +#ifdef LIBBSP_POWERPC_QORIQ_BSP_H + WR4(slot, SDHCI_SIGNAL_ENABLE, slot->intmask); +#endif +#endif /* __rtems__ */ +} + +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 | +#ifndef __rtems__ + SDHCI_INT_CARD_REMOVE | SDHCI_INT_CARD_INSERT | +#endif /* __rtems__ */ + 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) +{ +#ifndef __rtems__ + uint32_t res; + uint16_t clk; + int timeout; +#endif /* __rtems__ */ + + if (clock == slot->clock) + return; + slot->clock = clock; + +#ifndef __rtems__ + /* 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); +#else /* __rtems__ */ +#ifdef LIBBSP_POWERPC_QORIQ_BSP_H + +#define SYSCTL_CLOCK_DISABLE_MASK BSP_BMSK32(28, 31) +#define SYSCTL_CLOCK_MASK BSP_BMSK32(16, 31) +#define SYSCTL_SDCLKFS(val) BSP_BFLD32(val, 16, 23) +#define SYSCTL_DVS(val) BSP_BFLD32(val, 24, 27) +#define SYSCTL_SDCLKEN BSP_BBIT32(28) +#define SYSCTL_PEREN BSP_BBIT32(29) +#define SYSCTL_HCKEN BSP_BBIT32(30) +#define SYSCTL_IPGEN BSP_BBIT32(31) + + uint32_t sysctl = RD4(slot, SDHCI_CLOCK_CONTROL); + sysctl &= ~SYSCTL_CLOCK_DISABLE_MASK; + WR4(slot, SDHCI_CLOCK_CONTROL, sysctl); + + if (clock != 0) { + uint32_t sdclkfs_2 = 2; + uint32_t dvs_p1 = 1; + uint32_t base_clock = slot->max_clk; + + while (base_clock / sdclkfs_2 / 16 > clock && sdclkfs_2 < 256) { + sdclkfs_2 *= 2; + } + + while (base_clock / sdclkfs_2 / dvs_p1 > clock && dvs_p1 < 16) { + dvs_p1++; + } + + slot_printf( + slot, + "desired SD clock: %u, actual: %u\n", + clock, + base_clock / sdclkfs_2 / dvs_p1 + ); + + sysctl &= ~SYSCTL_CLOCK_MASK; + sysctl |= SYSCTL_SDCLKFS(sdclkfs_2 / 2) | SYSCTL_DVS(dvs_p1 - 1) | SYSCTL_SDCLKEN | SYSCTL_PEREN | SYSCTL_HCKEN | SYSCTL_IPGEN; + WR4(slot, SDHCI_CLOCK_CONTROL, sysctl); + + DELAY(10000); + } +#else + panic("FIXME"); +#endif +#endif /* __rtems__ */ +} + +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 { +#if defined(__rtems__) && defined(LIBBSP_POWERPC_QORIQ_BSP_H) + /* The DATPORT register use little-endian order */ + uint32_t *in = (uint32_t *) buffer; + while (left > 3) { + *in = RD4(slot, SDHCI_BUFFER); + ++in; + left -= 4; + } +#else /* __rtems__ */ + bus_read_multi_stream_4(slot->mem_res, SDHCI_BUFFER, + (uint32_t *)buffer, left >> 2); + left &= 3; +#endif /* __rtems__ */ + } + /* 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 { +#if defined(__rtems__) && defined(LIBBSP_POWERPC_QORIQ_BSP_H) + /* The DATPORT register use little-endian order */ + uint32_t *out = (uint32_t *) buffer; + while (left > 3) { + WR4(slot, SDHCI_BUFFER, *out); + ++out; + left -= 4; + } +#else /* __rtems__ */ + bus_write_multi_stream_4(slot->mem_res, SDHCI_BUFFER, + (uint32_t *)buffer, left >> 2); + left &= 3; +#endif /* __rtems__ */ + } + /* 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) +{ +#ifndef __rtems__ + struct sdhci_slot *slot = arg; + + taskqueue_enqueue(taskqueue_swi_giant, &slot->card_task); +#else /* __rtems__ */ + sdhci_card_task(arg, 0); +#endif /* __rtems__ */ +} + +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) +{ +#ifndef __rtems__ + 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); +#else /* __rtems__ */ + return (0); +#endif /* __rtems__ */ +} + +static int +sdhci_attach(device_t dev) +{ + struct sdhci_softc *sc = device_get_softc(dev); +#ifndef __rtems__ + uint32_t model; + uint16_t subvendor; + uint8_t class, subclass, progif; + int err, slots, bar, i; +#else /* __rtems__ */ + int slots, i; +#ifdef LIBBSP_POWERPC_QORIQ_BSP_H + rtems_status_code status_code; +#endif +#endif /* __rtems__ */ + + sc->dev = dev; +#ifndef __rtems__ + 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); + } +#else /* __rtems__ */ +#ifdef LIBBSP_POWERPC_QORIQ_BSP_H + /* FIXME */ + qoriq.esdhc.dcr = BSP_BBIT32(25); + sc->quirks = SDHCI_QUIRK_BROKEN_TIMINGS + | SDHCI_QUIRK_32BIT_DMA_SIZE; + slots = 1; +#else + slots = 0; +#endif +#endif /* __rtems__ */ +#ifndef __rtems__ + /* 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); + } +#endif /* __rtems__ */ + /* 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; +#ifndef __rtems__ + /* 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; + } +#else /* __rtems__ */ +#ifdef LIBBSP_POWERPC_QORIQ_BSP_H + slot->mem_res [0].r_bustag = 0; + slot->mem_res [0].r_bushandle = &qoriq.esdhc; +#endif +#endif /* __rtems__ */ +#ifndef __rtems__ + /* Allocate DMA tag. */ + err = bus_dma_tag_create(bus_get_dma_tag(dev), + DMA_BLOCK_SIZE, 0, BUS_SPACE_MAXADDR_32BIT, + BUS_SPACE_MAXADDR, NULL, NULL, + DMA_BLOCK_SIZE, 1, DMA_BLOCK_SIZE, + BUS_DMA_ALLOCNOW, NULL, NULL, + &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; + } +#endif /* __rtems__ */ + /* Initialize slot. */ + sdhci_init(slot); + caps = RD4(slot, SDHCI_CAPABILITIES); + /* Calculate base clock frequency. */ +#ifndef __rtems__ + 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; +#else /* __rtems__ */ +#ifdef LIBBSP_POWERPC_QORIQ_BSP_H + slot->max_clk = BSP_bus_frequency / 2; +#else + panic("FIXME"); +#endif +#endif /* __rtems__ */ + /* Calculate timeout clock frequency. */ +#ifndef __rtems__ + slot->timeout_clk = + (caps & SDHCI_TIMEOUT_CLK_MASK) >> SDHCI_TIMEOUT_CLK_SHIFT; +#else /* __rtems__ */ +#ifdef LIBBSP_POWERPC_QORIQ_BSP_H + slot->timeout_clk = slot->max_clk / 1000; +#else + panic("FIXME"); +#endif +#endif /* __rtems__ */ + 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; + +#ifndef __rtems__ + slot->host.f_min = slot->max_clk / 256; +#else /* __rtems__ */ +#ifdef LIBBSP_POWERPC_QORIQ_BSP_H + slot->host.f_min = 400000; +#else + panic("FIXME"); +#endif +#endif /* __rtems__ */ + 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; +#ifndef __rtems__ + 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; +#endif /* __rtems__ */ + + 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); + } + +#ifndef __rtems__ + TASK_INIT(&slot->card_task, 0, sdhci_card_task, slot); +#endif /* __rtems__ */ + callout_init(&slot->card_callout, 1); + sc->num_slots++; + } + device_printf(dev, "%d slot(s) allocated\n", sc->num_slots); + /* Activate the interrupt */ +#ifndef __rtems__ + err = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_MISC | INTR_MPSAFE, + NULL, sdhci_intr, sc, &sc->intrhand); + if (err) + device_printf(dev, "Can't setup IRQ\n"); + pci_enable_busmaster(dev); +#else /* __rtems__ */ +#ifdef LIBBSP_POWERPC_QORIQ_BSP_H + status_code = rtems_interrupt_server_handler_install( + RTEMS_ID_NONE, + QORIQ_IRQ_ESDHC, + "eSDHC", + RTEMS_INTERRUPT_UNIQUE, + sdhci_intr, + sc + ); + BSD_ASSERT(status_code == RTEMS_SUCCESSFUL); +#endif +#endif /* __rtems__ */ + /* 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) +{ +#ifndef __rtems__ + 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; + + callout_drain(&slot->card_callout); + taskqueue_drain(taskqueue_swi_giant, &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); + } +#else /* __rtems__ */ + panic("FIXME"); +#endif /* __rtems__ */ + 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; +#if defined(__rtems__) && defined(LIBBSP_POWERPC_QORIQ_BSP_H) + slot->hostctrl &= ~BSP_MSK8(2, 5); + slot->hostctrl |= BSP_FLD8(0x4, 2, 5); +#endif /* __rtems__ */ + 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); +} + +#if !defined(__rtems__) || !defined(RTEMS_BSD_SDHCI_QUIRK_32_BIT_REGS) +static void +sdhci_set_transfer_mode(struct sdhci_slot *slot, + struct mmc_data *data) +#else /* __rtems__ */ +static uint16_t +sdhci_get_transfer_mode(struct sdhci_slot *slot, + struct mmc_data *data) +#endif /* __rtems__ */ +{ + uint16_t mode; + + if (data == NULL) +#if !defined(__rtems__) || !defined(RTEMS_BSD_SDHCI_QUIRK_32_BIT_REGS) + return; +#else /* __rtems__ */ + return 0; +#endif /* __rtems__ */ + +#if !defined(__rtems__) || !defined(LIBBSP_POWERPC_QORIQ_BSP_H) + mode = SDHCI_TRNS_BLK_CNT_EN; + if (data->len > 512) + mode |= SDHCI_TRNS_MULTI; +#else /* __rtems__ */ + mode = 0; + if (data->len > 512) + mode |= SDHCI_TRNS_MULTI | SDHCI_TRNS_BLK_CNT_EN; +#endif /* __rtems__ */ + 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; + +#if !defined(__rtems__) || !defined(RTEMS_BSD_SDHCI_QUIRK_32_BIT_REGS) + WR2(slot, SDHCI_TRANSFER_MODE, mode); +#else /* __rtems__ */ + return mode; +#endif /* __rtems__ */ +} + +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) { +#if !defined(__rtems__) || !defined(RTEMS_BSD_SDHCI_QUIRK_NO_BUSY_IRQ) + WR4(slot, SDHCI_SIGNAL_ENABLE, + slot->intmask &= ~SDHCI_INT_RESPONSE); +#endif /* __rtems__ */ + } + /* Set command argument. */ + WR4(slot, SDHCI_ARGUMENT, cmd->arg); + /* Set data transfer mode. */ +#if !defined(__rtems__) || !defined(RTEMS_BSD_SDHCI_QUIRK_32_BIT_REGS) + sdhci_set_transfer_mode(slot, cmd->data); + /* Set command flags. */ + WR1(slot, SDHCI_COMMAND_FLAGS, flags); + /* Start command. */ + WR1(slot, SDHCI_COMMAND, cmd->opcode); +#else /* __rtems__ */ + uint16_t mode = sdhci_get_transfer_mode(slot, cmd->data); + WR4(slot, SDHCI_TRANSFER_MODE, (cmd->opcode << 24) | (flags << 16) | mode); +#endif /* __rtems__ */ +} + +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 defined(__rtems__) && defined(RTEMS_BSD_SDHCI_QUIRK_NO_BUSY_IRQ) + if (data == NULL) { +#else /* __rtems__ */ + if (data == NULL && (slot->curcmd->flags & MMC_RSP_BUSY) == 0) { +#endif /* __rtems__ */ + 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) { +#ifndef __rtems__ + slot_printf(slot, "Timeout too large!\n"); +#endif /* __rtems__ */ + 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, */ +#ifndef __rtems__ + if ((slot->sc->quirks & SDHCI_QUIRK_BROKEN_TIMINGS) && + (data->len <= 512)) +#else /* __rtems__ */ + if ((slot->sc->quirks & SDHCI_QUIRK_BROKEN_TIMINGS) && + (data->len < 512)) +#endif /* __rtems__ */ + 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) { +#ifndef __rtems__ + 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) +#else /* __rtems__ */ + WR4(slot, SDHCI_DMA_ADDRESS, (uint32_t) data->data); + if (data->len <= DMA_BLOCK_SIZE) +#endif /* __rtems__ */ + slot->intmask &= ~SDHCI_INT_DMA_END; + else + slot->intmask |= SDHCI_INT_DMA_END; +#ifndef __rtems__ + WR4(slot, SDHCI_SIGNAL_ENABLE, slot->intmask); + } +#else /* __rtems__ */ + slot->intmask &= ~(SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL); + } else { + slot->intmask |= SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL; + } + WR4(slot, SDHCI_INT_ENABLE, slot->intmask); + WR4(slot, SDHCI_SIGNAL_ENABLE, slot->intmask); +#endif /* __rtems__ */ + /* 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) +{ +#ifndef __rtems__ + struct mmc_data *data = slot->curcmd->data; +#endif /* __rtems__ */ + + 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); + } +#ifndef __rtems__ + /* 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); + } +#endif /* __rtems__ */ + /* 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); +#ifndef __rtems__ + if (dumping) { + while (slot->req != NULL) { + sdhci_intr(slot->sc); + DELAY(10); + } + } +#endif /* __rtems__ */ + 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) +{ +#ifndef __rtems__ + struct sdhci_slot *slot = device_get_ivars(reqdev); + int err = 0; + + SDHCI_LOCK(slot); + while (slot->bus_busy) + msleep(slot, &slot->mtx, 0, "sdhciah", 0); + slot->bus_busy++; + /* Activate led. */ + WR1(slot, SDHCI_HOST_CONTROL, slot->hostctrl |= SDHCI_CTRL_LED); + SDHCI_UNLOCK(slot); + return (err); +#else /* __rtems__ */ + return (0); +#endif /* __rtems__ */ +} + +static int +sdhci_release_host(device_t brdev, device_t reqdev) +{ +#ifndef __rtems__ + 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); +#endif /* __rtems__ */ + 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; +#ifndef __rtems__ + 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); + } +#endif /* __rtems__ */ + /* ... and reload it again. */ + slot->offset += DMA_BLOCK_SIZE; + left = data->len - slot->offset; +#ifndef __rtems__ + 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); + } +#endif /* __rtems__ */ + /* 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. */ +#ifndef __rtems__ + WR4(slot, SDHCI_DMA_ADDRESS, slot->paddr); +#else /* __rtems__ */ + WR4(slot, SDHCI_DMA_ADDRESS, (uint32_t) ((u_char*)data->data + slot->offset)); +#endif /* __rtems__ */ + } + /* 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); +#ifndef __rtems__ + taskqueue_enqueue(taskqueue_swi_giant, + &slot->card_task); +#endif /* __rtems__ */ + } + 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) { +#ifndef __rtems__ + 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 /* __rtems__ */ + slot->host.ios.clock = value; +#endif /* __rtems__ */ + } 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; + + +#ifndef __rtems__ +DRIVER_MODULE(sdhci, pci, sdhci_driver, sdhci_devclass, 0, 0); +#else /* __rtems__ */ +DRIVER_MODULE(sdhci, nexus, sdhci_driver, sdhci_devclass, 0, 0); +#endif /* __rtems__ */ diff --git a/freebsd/sys/dev/sdhci/sdhci.h b/freebsd/sys/dev/sdhci/sdhci.h new file mode 100644 index 00000000..a0ad1335 --- /dev/null +++ b/freebsd/sys/dev/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$ + */ + +/* + * 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 diff --git a/rtemsbsd/include/bsp/nexus-devices.h b/rtemsbsd/include/bsp/nexus-devices.h index 4d6e767e..f615e4e4 100644 --- a/rtemsbsd/include/bsp/nexus-devices.h +++ b/rtemsbsd/include/bsp/nexus-devices.h @@ -88,6 +88,13 @@ RTEMS_BSD_DEFINE_NEXUS_DEVICE(cgem, 0, RTEMS_ARRAY_SIZE(cgem0_res), SYSINIT_DRIVER_REFERENCE(e1000phy, miibus); +#elif defined(LIBBSP_ARM_ALTERA_CYCLONE_V_BSP_H) + +RTEMS_BSD_DEFINE_NEXUS_DEVICE(dw_mmc, 0, 0, NULL); + +SYSINIT_DRIVER_REFERENCE(mmc, dw_mmc); +SYSINIT_DRIVER_REFERENCE(mmcsd, mmc); + #elif defined(LIBBSP_POWERPC_QORIQ_BSP_H) #if !QORIQ_CHIP_IS_T_VARIANT(QORIQ_CHIP_VARIANT) diff --git a/rtemsbsd/include/rtems/bsd/local/mmcbr_if.h b/rtemsbsd/include/rtems/bsd/local/mmcbr_if.h new file mode 100644 index 00000000..f11e201d --- /dev/null +++ b/rtemsbsd/include/rtems/bsd/local/mmcbr_if.h @@ -0,0 +1,79 @@ +/* + * This file is produced automatically. + * Do not modify anything in here by hand. + * + * Created from source file + * dev/mmc/mmcbr_if.m + * with + * makeobjops.awk + * + * See the source file for legal information + */ + + +#ifndef _mmcbr_if_h_ +#define _mmcbr_if_h_ + +/** @brief Unique descriptor for the MMCBR_UPDATE_IOS() method */ +extern struct kobjop_desc mmcbr_update_ios_desc; +/** @brief A function implementing the MMCBR_UPDATE_IOS() method */ +typedef int mmcbr_update_ios_t(device_t brdev, device_t reqdev); + +static __inline int MMCBR_UPDATE_IOS(device_t brdev, device_t reqdev) +{ + kobjop_t _m; + KOBJOPLOOKUP(((kobj_t)brdev)->ops,mmcbr_update_ios); + return ((mmcbr_update_ios_t *) _m)(brdev, reqdev); +} + +/** @brief Unique descriptor for the MMCBR_REQUEST() method */ +extern struct kobjop_desc mmcbr_request_desc; +/** @brief A function implementing the MMCBR_REQUEST() method */ +typedef int mmcbr_request_t(device_t brdev, device_t reqdev, + struct mmc_request *req); + +static __inline int MMCBR_REQUEST(device_t brdev, device_t reqdev, + struct mmc_request *req) +{ + kobjop_t _m; + KOBJOPLOOKUP(((kobj_t)brdev)->ops,mmcbr_request); + return ((mmcbr_request_t *) _m)(brdev, reqdev, req); +} + +/** @brief Unique descriptor for the MMCBR_GET_RO() method */ +extern struct kobjop_desc mmcbr_get_ro_desc; +/** @brief A function implementing the MMCBR_GET_RO() method */ +typedef int mmcbr_get_ro_t(device_t brdev, device_t reqdev); + +static __inline int MMCBR_GET_RO(device_t brdev, device_t reqdev) +{ + kobjop_t _m; + KOBJOPLOOKUP(((kobj_t)brdev)->ops,mmcbr_get_ro); + return ((mmcbr_get_ro_t *) _m)(brdev, reqdev); +} + +/** @brief Unique descriptor for the MMCBR_ACQUIRE_HOST() method */ +extern struct kobjop_desc mmcbr_acquire_host_desc; +/** @brief A function implementing the MMCBR_ACQUIRE_HOST() method */ +typedef int mmcbr_acquire_host_t(device_t brdev, device_t reqdev); + +static __inline int MMCBR_ACQUIRE_HOST(device_t brdev, device_t reqdev) +{ + kobjop_t _m; + KOBJOPLOOKUP(((kobj_t)brdev)->ops,mmcbr_acquire_host); + return ((mmcbr_acquire_host_t *) _m)(brdev, reqdev); +} + +/** @brief Unique descriptor for the MMCBR_RELEASE_HOST() method */ +extern struct kobjop_desc mmcbr_release_host_desc; +/** @brief A function implementing the MMCBR_RELEASE_HOST() method */ +typedef int mmcbr_release_host_t(device_t brdev, device_t reqdev); + +static __inline int MMCBR_RELEASE_HOST(device_t brdev, device_t reqdev) +{ + kobjop_t _m; + KOBJOPLOOKUP(((kobj_t)brdev)->ops,mmcbr_release_host); + return ((mmcbr_release_host_t *) _m)(brdev, reqdev); +} + +#endif /* _mmcbr_if_h_ */ diff --git a/rtemsbsd/include/rtems/bsd/local/mmcbus_if.h b/rtemsbsd/include/rtems/bsd/local/mmcbus_if.h new file mode 100644 index 00000000..5e0e2da0 --- /dev/null +++ b/rtemsbsd/include/rtems/bsd/local/mmcbus_if.h @@ -0,0 +1,55 @@ +/* + * This file is produced automatically. + * Do not modify anything in here by hand. + * + * Created from source file + * dev/mmc/mmcbus_if.m + * with + * makeobjops.awk + * + * See the source file for legal information + */ + + +#ifndef _mmcbus_if_h_ +#define _mmcbus_if_h_ + +/** @brief Unique descriptor for the MMCBUS_WAIT_FOR_REQUEST() method */ +extern struct kobjop_desc mmcbus_wait_for_request_desc; +/** @brief A function implementing the MMCBUS_WAIT_FOR_REQUEST() method */ +typedef int mmcbus_wait_for_request_t(device_t brdev, device_t reqdev, + struct mmc_request *req); + +static __inline int MMCBUS_WAIT_FOR_REQUEST(device_t brdev, device_t reqdev, + struct mmc_request *req) +{ + kobjop_t _m; + KOBJOPLOOKUP(((kobj_t)brdev)->ops,mmcbus_wait_for_request); + return ((mmcbus_wait_for_request_t *) _m)(brdev, reqdev, req); +} + +/** @brief Unique descriptor for the MMCBUS_ACQUIRE_BUS() method */ +extern struct kobjop_desc mmcbus_acquire_bus_desc; +/** @brief A function implementing the MMCBUS_ACQUIRE_BUS() method */ +typedef int mmcbus_acquire_bus_t(device_t brdev, device_t reqdev); + +static __inline int MMCBUS_ACQUIRE_BUS(device_t brdev, device_t reqdev) +{ + kobjop_t _m; + KOBJOPLOOKUP(((kobj_t)brdev)->ops,mmcbus_acquire_bus); + return ((mmcbus_acquire_bus_t *) _m)(brdev, reqdev); +} + +/** @brief Unique descriptor for the MMCBUS_RELEASE_BUS() method */ +extern struct kobjop_desc mmcbus_release_bus_desc; +/** @brief A function implementing the MMCBUS_RELEASE_BUS() method */ +typedef int mmcbus_release_bus_t(device_t brdev, device_t reqdev); + +static __inline int MMCBUS_RELEASE_BUS(device_t brdev, device_t reqdev) +{ + kobjop_t _m; + KOBJOPLOOKUP(((kobj_t)brdev)->ops,mmcbus_release_bus); + return ((mmcbus_release_bus_t *) _m)(brdev, reqdev); +} + +#endif /* _mmcbus_if_h_ */ diff --git a/rtemsbsd/local/mmcbr_if.c b/rtemsbsd/local/mmcbr_if.c new file mode 100644 index 00000000..1ce2d263 --- /dev/null +++ b/rtemsbsd/local/mmcbr_if.c @@ -0,0 +1,64 @@ +#include + +/* + * This file is produced automatically. + * Do not modify anything in here by hand. + * + * Created from source file + * freebsd-org/sys/dev/mmc/mmcbr_if.m + * with + * makeobjops.awk + * + * See the source file for legal information + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct kobj_method mmcbr_update_ios_method_default = { + &mmcbr_update_ios_desc, (kobjop_t) kobj_error_method +}; + +struct kobjop_desc mmcbr_update_ios_desc = { + 0, &mmcbr_update_ios_method_default +}; + +struct kobj_method mmcbr_request_method_default = { + &mmcbr_request_desc, (kobjop_t) kobj_error_method +}; + +struct kobjop_desc mmcbr_request_desc = { + 0, &mmcbr_request_method_default +}; + +struct kobj_method mmcbr_get_ro_method_default = { + &mmcbr_get_ro_desc, (kobjop_t) kobj_error_method +}; + +struct kobjop_desc mmcbr_get_ro_desc = { + 0, &mmcbr_get_ro_method_default +}; + +struct kobj_method mmcbr_acquire_host_method_default = { + &mmcbr_acquire_host_desc, (kobjop_t) kobj_error_method +}; + +struct kobjop_desc mmcbr_acquire_host_desc = { + 0, &mmcbr_acquire_host_method_default +}; + +struct kobj_method mmcbr_release_host_method_default = { + &mmcbr_release_host_desc, (kobjop_t) kobj_error_method +}; + +struct kobjop_desc mmcbr_release_host_desc = { + 0, &mmcbr_release_host_method_default +}; + diff --git a/rtemsbsd/local/mmcbus_if.c b/rtemsbsd/local/mmcbus_if.c new file mode 100644 index 00000000..a757a561 --- /dev/null +++ b/rtemsbsd/local/mmcbus_if.c @@ -0,0 +1,47 @@ +#include + +/* + * This file is produced automatically. + * Do not modify anything in here by hand. + * + * Created from source file + * freebsd-org/sys/dev/mmc/mmcbus_if.m + * with + * makeobjops.awk + * + * See the source file for legal information + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct kobj_method mmcbus_wait_for_request_method_default = { + &mmcbus_wait_for_request_desc, (kobjop_t) kobj_error_method +}; + +struct kobjop_desc mmcbus_wait_for_request_desc = { + 0, &mmcbus_wait_for_request_method_default +}; + +struct kobj_method mmcbus_acquire_bus_method_default = { + &mmcbus_acquire_bus_desc, (kobjop_t) kobj_error_method +}; + +struct kobjop_desc mmcbus_acquire_bus_desc = { + 0, &mmcbus_acquire_bus_method_default +}; + +struct kobj_method mmcbus_release_bus_method_default = { + &mmcbus_release_bus_desc, (kobjop_t) kobj_error_method +}; + +struct kobjop_desc mmcbus_release_bus_desc = { + 0, &mmcbus_release_bus_method_default +}; + diff --git a/rtemsbsd/rtems/rtems-bsd-cam.c b/rtemsbsd/rtems/rtems-bsd-cam.c index ab15f426..0f518586 100644 --- a/rtemsbsd/rtems/rtems-bsd-cam.c +++ b/rtemsbsd/rtems/rtems-bsd-cam.c @@ -151,7 +151,7 @@ rtems_bsd_scsi_inquiry(union ccb *ccb, struct scsi_inquiry_data *inq_data) rtems_bsd_ccb_callback, BSD_SCSI_TAG, (u_int8_t *) inq_data, - sizeof(*inq_data) - 1, + SHORT_INQUIRY_LENGTH, FALSE, 0, SSD_MIN_SIZE, @@ -339,11 +339,11 @@ rtems_bsd_sim_attach_worker(rtems_media_state state, const char *src, char **des } sc = rtems_bsd_scsi_inquiry(&sim->ccb, &inq_data); - if (sc != RTEMS_SUCCESSFUL) { + if (sc == RTEMS_SUCCESSFUL) { + scsi_print_inquiry(&inq_data); + } else { BSD_PRINTF("OOPS: inquiry failed\n"); - goto error; } - scsi_print_inquiry(&inq_data); for (retries = 0; retries <= 3; ++retries) { sc = rtems_bsd_scsi_test_unit_ready(&sim->ccb); @@ -353,10 +353,14 @@ rtems_bsd_sim_attach_worker(rtems_media_state state, const char *src, char **des } if (sc != RTEMS_SUCCESSFUL) { BSD_PRINTF("OOPS: test unit ready failed\n"); - goto error; } - sc = rtems_bsd_scsi_read_capacity(&sim->ccb, &block_count, &block_size); + for (retries = 0; retries <= 3; ++retries) { + sc = rtems_bsd_scsi_read_capacity(&sim->ccb, &block_count, &block_size); + if (sc == RTEMS_SUCCESSFUL) { + break; + } + } if (sc != RTEMS_SUCCESSFUL) { BSD_PRINTF("OOPS: read capacity failed\n"); goto error; diff --git a/rtemsbsd/rtems/rtems-bsd-configintrhook.c b/rtemsbsd/rtems/rtems-bsd-configintrhook.c new file mode 100644 index 00000000..baf737e1 --- /dev/null +++ b/rtemsbsd/rtems/rtems-bsd-configintrhook.c @@ -0,0 +1,58 @@ +/** + * @file + * + * @ingroup rtems_bsd_rtems + * + * @brief TODO. + */ + +/* + * Copyright (c) 2011 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Dornierstr. 4 + * 82178 Puchheim + * Germany + * + * + * 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 COPYRIGHT HOLDERS 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 COPYRIGHT + * OWNER 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. + */ + +#include + +#include +#include +#include + +int +config_intrhook_establish(struct intr_config_hook *hook) +{ + (*hook->ich_func)(hook->ich_arg); + + return (0); +} + +void +config_intrhook_disestablish(struct intr_config_hook *hook) +{ + /* Do nothing */ +} diff --git a/rtemsbsd/sys/dev/dw_mmc/dw_mmc.c b/rtemsbsd/sys/dev/dw_mmc/dw_mmc.c new file mode 100644 index 00000000..681bf95a --- /dev/null +++ b/rtemsbsd/sys/dev/dw_mmc/dw_mmc.c @@ -0,0 +1,1106 @@ +#include + +/*- + * Copyright (c) 2006 Bernd Walter. All rights reserved. + * Copyright (c) 2006 M. Warner Losh. All rights reserved. + * Copyright (c) 2010 Greg Ansley. All rights reserved. + * Copyright (c) 2014 embedded brains GmbH. 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 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 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#include + +#include + +#ifdef LIBBSP_ARM_ALTERA_CYCLONE_V_BSP_H + +#define DW_MMC_ALTERA_CYCLONE_V + +#include +#include +#include +#include +#include + +#endif /* DW_MMC_ALTERA_CYCLONE_V */ + +struct dw_mmc_softc { + device_t dev; + struct mtx sc_mtx; + struct mtx bus_mtx; + bus_space_handle_t bushandle; + uint32_t biu_clock; + uint32_t ciu_clock; + uint32_t card_clock; + struct mmc_host host; + uint32_t cmdr_flags; + volatile struct dw_mmc_des *des; + rtems_id task_id; +}; + +#define DW_MMC_MAX_DES_COUNT 32 + +#define DW_MMC_MAX_DMA_TRANSFER_BYTES \ + (DW_MMC_MAX_DES_COUNT * 2 * DW_MMC_DES1_MAX_BS) + +static inline uint32_t +RD4(struct dw_mmc_softc *sc, bus_size_t off) +{ + return (bus_space_read_4(0, sc->bushandle, off)); +} + +static inline void +WR4(struct dw_mmc_softc *sc, bus_size_t off, uint32_t val) +{ + bus_space_write_4(0, sc->bushandle, off, val); +} + +/* bus entry points */ +static int dw_mmc_probe(device_t dev); +static int dw_mmc_attach(device_t dev); +static int dw_mmc_detach(device_t dev); +static void dw_mmc_intr(void *); + +static void +DW_MMC_LOCK(struct dw_mmc_softc *sc) +{ + mtx_lock(&sc->sc_mtx); + sc->task_id = rtems_task_self(); +} + +#define DW_MMC_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) +#define DW_MMC_LOCK_INIT(_sc) \ + mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \ + "dw_mmc", MTX_DEF) + +#define DW_MMC_BUS_LOCK(_sc) mtx_lock(&(_sc)->bus_mtx) +#define DW_MMC_BUS_UNLOCK(_sc) mtx_unlock(&(_sc)->bus_mtx) +#define DW_MMC_BUS_LOCK_INIT(_sc) \ + mtx_init(&_sc->bus_mtx, device_get_nameunit(_sc->dev), \ + "dw_mmc", MTX_DEF) + +static int +dw_mmc_poll_reset_completion(struct dw_mmc_softc *sc, uint32_t ctrl_resets) +{ + rtems_interval timeout = rtems_clock_tick_later_usec(250000); + + do { + if ((RD4(sc, DW_MMC_CTRL) & ctrl_resets) == 0) { + return 0; + } + } while (rtems_clock_tick_before(timeout)); + + return EBUSY; +} + +static uint32_t +dw_mmc_poll_intsts(struct dw_mmc_softc *sc, uint32_t mask) +{ + uint32_t ret_intsts = 0; + + while (1) { + uint32_t intsts = RD4(sc, DW_MMC_RINTSTS); + + if ((intsts & DW_MMC_INT_ERROR) != 0) { + WR4(sc, DW_MMC_RINTSTS, intsts); + ret_intsts = intsts; + break; + } + + if ((intsts & mask) != 0) { + WR4(sc, DW_MMC_RINTSTS, intsts & mask); + break; + } + } + + return ret_intsts; +} + +static void +dw_mmc_wait_for_interrupt(struct dw_mmc_softc *sc, uint32_t intmask) +{ + rtems_status_code rs; + + WR4(sc, DW_MMC_INTMASK, intmask); + + rs = rtems_event_transient_receive(RTEMS_WAIT, RTEMS_NO_TIMEOUT); + BSD_ASSERT(rs == RTEMS_SUCCESSFUL); +} + +static int +dw_mmc_init(struct dw_mmc_softc *sc) +{ + uint32_t ctrl; + uint32_t fifoth; + int err; + + err = dw_mmc_poll_reset_completion(sc, DW_MMC_CTRL_RESET); + if (err != 0) { + return err; + } + + sc->card_clock = UINT32_MAX; + + /* Clear interrupt status */ + WR4(sc, DW_MMC_RINTSTS, 0xffffffff); + + /* Disable all interrupts */ + WR4(sc, DW_MMC_INTMASK, 0x0); + + /* Enable interrupts in general */ + ctrl = RD4(sc, DW_MMC_CTRL); + ctrl |= DW_MMC_CTRL_INT_ENABLE; + WR4(sc, DW_MMC_CTRL, ctrl); + + /* Set data and response timeout to maximum values */ + WR4(sc, DW_MMC_TMOUT, 0xffffffff); + + /* Set debounce value to 25ms */ + WR4(sc, DW_MMC_DEBNCE, (sc->biu_clock / 1000) * 25); + + /* Set FIFO watermarks */ + fifoth = RD4(sc, DW_MMC_FIFOTH); + fifoth &= ~(DW_MMC_FIFOTH_RX_WMARK_MSK | DW_MMC_FIFOTH_TX_WMARK_MSK); + fifoth |= DW_MMC_FIFOTH_RX_WMARK(511) | DW_MMC_FIFOTH_TX_WMARK(512); + WR4(sc, DW_MMC_FIFOTH, fifoth); + + /* Set DMA descriptor */ + WR4(sc, DW_MMC_DBADDR, (uint32_t) sc->des); + + return 0; +} + +static void +dw_mmc_fini(struct dw_mmc_softc *sc) +{ + WR4(sc, DW_MMC_CTRL, DW_MMC_CTRL_FIFO_RESET | DW_MMC_CTRL_RESET); +} + +static int +dw_mmc_probe(device_t dev) +{ + + device_set_desc(dev, "DesignWare Mobile Storage Host"); + return (0); +} + +static int +dw_mmc_platform_init(struct dw_mmc_softc *sc) +{ +#ifdef DW_MMC_ALTERA_CYCLONE_V + size_t des_size = DW_MMC_MAX_DES_COUNT * sizeof(*sc->des); + ALT_STATUS_CODE as; + + /* Module base address */ + sc->bushandle = (bus_space_handle_t) ALT_SDMMC_ADDR; + + /* BIU clock */ + as = alt_clk_freq_get(ALT_CLK_L4_MP, &sc->biu_clock); + BSD_ASSERT(as == ALT_E_SUCCESS); + + /* CIU clock */ + as = alt_clk_clock_enable(ALT_CLK_SDMMC); + BSD_ASSERT(as == ALT_E_SUCCESS); + as = alt_clk_freq_get(ALT_CLK_SDMMC, &sc->ciu_clock); + BSD_ASSERT(as == ALT_E_SUCCESS); + sc->ciu_clock /= 4; + + sc->des = rtems_cache_coherent_allocate(des_size, 0, 0); + if (sc->des == NULL) { + return (ENOMEM); + } + memset(__DEVOLATILE(void *, sc->des), 0, des_size); +#endif + + return (0); +} + +static void +dw_mmc_platform_install_intr(struct dw_mmc_softc *sc) +{ + rtems_vector_number irq = +#ifdef DW_MMC_ALTERA_CYCLONE_V + ALT_INT_INTERRUPT_SDMMC_IRQ; +#else + UINT32_MAX; +#endif + rtems_status_code rs; + + /* + * Activate the interrupt + */ + rs = rtems_interrupt_handler_install(irq, "DW MMC", + RTEMS_INTERRUPT_SHARED, dw_mmc_intr, sc); + BSD_ASSERT(rs == RTEMS_SUCCESSFUL); +} + +static bool +dw_mmc_platform_set_clock(struct dw_mmc_softc *sc, uint32_t card_clock) +{ + bool use_hold_reg; + +#ifdef DW_MMC_ALTERA_CYCLONE_V + uint32_t drvsel; + uint32_t smplsel; + uint32_t ctl; + ALT_STATUS_CODE as; + + /* FIXME: Values taken from U-Boot, not clear how they are determined */ + if (card_clock > 25000000) { + drvsel = 3; + smplsel = 7; + } else { + drvsel = 3; + smplsel = 0; + } + + use_hold_reg = drvsel != 0; + + as = alt_clk_clock_disable(ALT_CLK_SDMMC); + BSD_ASSERT(as == ALT_E_SUCCESS); + + ctl = alt_read_word(ALT_SYSMGR_SDMMC_CTL_ADDR); + ctl &= ALT_SYSMGR_SDMMC_CTL_DRVSEL_CLR_MSK + & ALT_SYSMGR_SDMMC_CTL_SMPLSEL_CLR_MSK; + ctl |= ALT_SYSMGR_SDMMC_CTL_DRVSEL_SET(drvsel) + | ALT_SYSMGR_SDMMC_CTL_SMPLSEL_SET(smplsel); + alt_write_word(ALT_SYSMGR_SDMMC_CTL_ADDR, ctl); + + as = alt_clk_clock_enable(ALT_CLK_SDMMC); + BSD_ASSERT(as == ALT_E_SUCCESS); +#else + use_hold_reg = false; +#endif + + return use_hold_reg; +} + +static void +dw_mmc_platform_fini(struct dw_mmc_softc *sc) +{ +#ifdef DW_MMC_ALTERA_CYCLONE_V + rtems_cache_coherent_free(__DEVOLATILE(void *, sc->des)); +#endif +} + +static int +dw_mmc_attach(device_t dev) +{ + struct dw_mmc_softc *sc = device_get_softc(dev); + int err; + + sc->dev = dev; + + err = dw_mmc_platform_init(sc); + if (err != 0) { + return (err); + } + + dw_mmc_fini(sc); + err = dw_mmc_init(sc); + if (err != 0) { + dw_mmc_platform_fini(sc); + + return (err); + } + + DW_MMC_LOCK_INIT(sc); + DW_MMC_BUS_LOCK_INIT(sc); + + dw_mmc_platform_install_intr(sc); + + sc->host.f_min = 400000; + sc->host.f_max = (int) sc->ciu_clock; + if (sc->host.f_max > 50000000) + sc->host.f_max = 50000000; /* Limit to 50MHz */ + + sc->host.host_ocr = MMC_OCR_320_330 | MMC_OCR_330_340; + + /* FIXME: MMC_CAP_8_BIT_DATA for eSDIO? */ + sc->host.caps = MMC_CAP_4_BIT_DATA | MMC_CAP_HSPEED; + + device_add_child(dev, "mmc", 0); + device_set_ivars(dev, &sc->host); + err = bus_generic_attach(dev); + + return (err); +} + +static int +dw_mmc_detach(device_t dev) +{ + struct dw_mmc_softc *sc = device_get_softc(dev); + + dw_mmc_fini(sc); + + /* FIXME: Implement */ + BSD_ASSERT(0); + + return (EBUSY); +} + +static int +dw_mmc_cmd_wait(struct dw_mmc_softc *sc) +{ + rtems_interval timeout = rtems_clock_tick_later_usec(250000); + + do { + if ((RD4(sc, DW_MMC_CMD) & DW_MMC_CMD_START) == 0) { + return 0; + } + } while (rtems_clock_tick_before(timeout)); + + return EBUSY; +} + +static void +dw_mmc_cmd_start(struct dw_mmc_softc *sc, uint32_t cmd, uint32_t cmdarg) +{ + WR4(sc, DW_MMC_CMDARG, cmdarg); + cmd |= DW_MMC_CMD_START; + WR4(sc, DW_MMC_CMD, cmd); +} + +static int +dw_mmc_cmd_update_clock(struct dw_mmc_softc *sc) +{ + dw_mmc_cmd_start(sc, + DW_MMC_CMD_UPDATE_CLK | DW_MMC_CMD_PRV_DATA_WAIT, 0); + + return dw_mmc_cmd_wait(sc); +} + +static int +dw_mmc_set_clock(struct dw_mmc_softc *sc, uint32_t card_clock) +{ + uint32_t clkdiv; + int err; + + if (sc->card_clock == card_clock) { + return 0; + } + + sc->card_clock = card_clock; + + /* Disable card clock */ + WR4(sc, DW_MMC_CLKENA, 0); + + err = dw_mmc_cmd_update_clock(sc); + if (err != 0) { + return err; + } + + if (card_clock == 0) { + return 0; + } + + if (dw_mmc_platform_set_clock(sc, card_clock)) { + sc->cmdr_flags |= DW_MMC_CMD_USE_HOLD_REG; + } else { + sc->cmdr_flags &= ~DW_MMC_CMD_USE_HOLD_REG; + } + + if (card_clock == sc->ciu_clock) { + clkdiv = 0; + } else { + uint32_t s = 2 * card_clock; + + clkdiv = (sc->ciu_clock + s - 1) / s; + } + + WR4(sc, DW_MMC_CLKDIV, clkdiv); + WR4(sc, DW_MMC_CLKSRC, 0); + + err = dw_mmc_cmd_update_clock(sc); + if (err != 0) { + return err; + } + + /* Enable card clock */ + WR4(sc, DW_MMC_CLKENA, DW_MMC_CLKEN_ENABLE); + + return dw_mmc_cmd_update_clock(sc); +} + +static int +dw_mmc_update_ios(device_t brdev, device_t reqdev) +{ + struct dw_mmc_softc *sc = device_get_softc(brdev); + struct mmc_host *host; + struct mmc_ios *ios; + uint32_t ctype; + int err; + + DW_MMC_LOCK(sc); + + host = &sc->host; + ios = &host->ios; + + err = dw_mmc_set_clock(sc, (uint32_t) ios->clock); + if (err != 0) { + return (err); + } + + if (ios->power_mode == power_off) { + WR4(sc, DW_MMC_PWREN, 0); + } else { + sc->cmdr_flags |= DW_MMC_CMD_SEND_INIT; + WR4(sc, DW_MMC_PWREN, DW_MMC_PWREN_ENABLE); + } + + switch (ios->bus_width) { + default: + BSD_ASSERT(ios->bus_width == bus_width_1); + ctype = DW_MMC_CTYPE_1BIT; + break; + case bus_width_4: + ctype = DW_MMC_CTYPE_4BIT; + break; + case bus_width_8: + ctype = DW_MMC_CTYPE_8BIT; + break; + } + + WR4(sc, DW_MMC_CTYPE, ctype); + + DW_MMC_UNLOCK(sc); + + return (0); +} + +static int +dw_mmc_fifo_and_dma_reset(struct dw_mmc_softc *sc) +{ + uint32_t ctrl_resets = DW_MMC_CTRL_FIFO_RESET | DW_MMC_CTRL_DMA_RESET; + uint32_t ctrl = RD4(sc, DW_MMC_CTRL); + + ctrl |= ctrl_resets; + + WR4(sc, DW_MMC_CTRL, ctrl); + + return dw_mmc_poll_reset_completion(sc, ctrl_resets); +} + +static int +dw_mmc_cmd_read_response(struct dw_mmc_softc *sc, struct mmc_command *cmd, + uint32_t intsts) +{ + if ((intsts & DW_MMC_INT_RTO) != 0) { + return MMC_ERR_TIMEOUT; + } else if ((intsts & DW_MMC_INT_RCRC) != 0 + && (cmd->flags & MMC_RSP_CRC) != 0) { + return MMC_ERR_BADCRC; + } else if ((intsts & DW_MMC_INT_RE) != 0) { + return MMC_ERR_FAILED; + } + + if ((cmd->flags & MMC_RSP_PRESENT) != 0) { + uint32_t *resp = &cmd->resp[0]; + + if ((cmd->flags & MMC_RSP_136) != 0) { + resp[3] = RD4(sc, DW_MMC_RESP0); + resp[2] = RD4(sc, DW_MMC_RESP1); + resp[1] = RD4(sc, DW_MMC_RESP2); + resp[0] = RD4(sc, DW_MMC_RESP3); + } else { + resp[0] = RD4(sc, DW_MMC_RESP0); + } + } + + return MMC_ERR_NONE; +} + +static uint32_t +dw_mmc_cmd_data_read(struct dw_mmc_softc *sc, struct mmc_data *data, + uint32_t *data32, size_t count_bytes) +{ + uint32_t intsts = 0; + + while (count_bytes > 0) { + uint32_t status; + size_t available_words; + size_t dangling_bytes = 0; + size_t i; + + intsts = dw_mmc_poll_intsts(sc, DW_MMC_INT_RXDR + | DW_MMC_INT_DTO | DW_MMC_INT_HTO); + + if (intsts != 0) { + return intsts; + } + + status = RD4(sc, DW_MMC_STATUS); + available_words = DW_MMC_STATUS_GET_FIFO_CNT(status); + + if (available_words * DW_MMC_FIFO_WIDTH > count_bytes) { + dangling_bytes = count_bytes % DW_MMC_FIFO_WIDTH; + --available_words; + } + + for (i = 0; i < available_words; i++) { + data32[i] = RD4(sc, DW_MMC_DATA); + } + + data32 += available_words; + count_bytes -= available_words * DW_MMC_FIFO_WIDTH; + + if (dangling_bytes != 0) { + uint32_t tmp = RD4(sc, DW_MMC_DATA); + + memcpy(data32, &tmp, dangling_bytes); + BSD_ASSERT(count_bytes == dangling_bytes); + count_bytes = 0; + } + } + + if ((data->flags & MMC_DATA_MULTI) != 0) { + intsts = dw_mmc_poll_intsts(sc, DW_MMC_INT_ACD); + } + + return intsts; +} + +static uint32_t +dw_mmc_cmd_data_write(struct dw_mmc_softc *sc, struct mmc_data *data, + uint32_t *data32, size_t count_bytes) +{ + uint32_t intsts; + + while (count_bytes > 0) { + uint32_t status; + size_t pending_words = count_bytes / DW_MMC_FIFO_WIDTH; + size_t free_words; + size_t dangling_bytes; + size_t words_to_write; + size_t i; + + intsts = dw_mmc_poll_intsts(sc, DW_MMC_INT_TXDR + | DW_MMC_INT_HTO); + + if (intsts != 0) { + return intsts; + } + + status = RD4(sc, DW_MMC_STATUS); + free_words = DW_MMC_FIFO_DEPTH - DW_MMC_STATUS_GET_FIFO_CNT(status); + + if (pending_words >= free_words) { + words_to_write = free_words; + dangling_bytes = 0; + } else { + words_to_write = pending_words; + dangling_bytes = count_bytes % DW_MMC_FIFO_WIDTH; + } + + for (i = 0; i < words_to_write; i++) { + WR4(sc, DW_MMC_DATA, data32[i]); + } + + data32 += words_to_write; + count_bytes -= words_to_write * DW_MMC_FIFO_WIDTH; + + if (dangling_bytes != 0) { + uint32_t tmp = 0; + + memcpy(&tmp, &data32[0], dangling_bytes); + WR4(sc, DW_MMC_DATA, tmp); + BSD_ASSERT(count_bytes == dangling_bytes); + count_bytes = 0; + } + } + + intsts = dw_mmc_poll_intsts(sc, DW_MMC_INT_DTO); + + if ((data->flags & MMC_DATA_MULTI) != 0 && intsts == 0) { + dw_mmc_poll_intsts(sc, DW_MMC_INT_ACD); + } + + return intsts; +} + +static uint32_t +dw_mmc_cmd_data_transfer(struct dw_mmc_softc *sc, struct mmc_data *data, + size_t done_bytes, bool use_dma) +{ + uint32_t *data32 = (uint32_t *) ((char *) data->data + done_bytes); + bool do_write = (data->flags & MMC_DATA_WRITE) != 0; + uint32_t intsts; + + if (use_dma) { + dw_mmc_wait_for_interrupt(sc, DW_MMC_INT_DTO); + intsts = dw_mmc_poll_intsts(sc, DW_MMC_INT_DTO); + + if ((data->flags & MMC_DATA_MULTI) != 0 && intsts == 0) { + dw_mmc_poll_intsts(sc, DW_MMC_INT_ACD); + } + + if (!do_write) { + rtems_cache_invalidate_multiple_data_lines(data->data, + data->len); + } + } else { + size_t count_bytes = data->len - done_bytes; + + if (do_write) { + intsts = dw_mmc_cmd_data_write(sc, data, data32, count_bytes); + } else { + intsts = dw_mmc_cmd_data_read(sc, data, data32, count_bytes); + } + } + + return intsts; +} + +static int +dw_mmc_cmd_data_finish(struct dw_mmc_softc *sc, uint32_t intsts) +{ + int mmc_err = MMC_ERR_NONE; + + if ((intsts & DW_MMC_INT_ERROR) != 0) { + if ((intsts & DW_MMC_INT_DCRC) != 0) { + mmc_err = MMC_ERR_BADCRC; + } else if ((intsts & DW_MMC_INT_EBE) != 0) { + mmc_err = MMC_ERR_FAILED; + } else if ((intsts & DW_MMC_INT_DRTO) != 0) { + mmc_err = MMC_ERR_TIMEOUT; + } else { + mmc_err = MMC_ERR_FAILED; + } + } + + return mmc_err; +} + +static int +dw_mmc_cmd_done(struct dw_mmc_softc *sc, struct mmc_command *cmd, + struct mmc_data *data, size_t done_bytes, bool use_dma) +{ + uint32_t intsts; + int mmc_err; + + dw_mmc_wait_for_interrupt(sc, DW_MMC_INT_CMD_DONE); + + intsts = RD4(sc, DW_MMC_RINTSTS); + WR4(sc, DW_MMC_RINTSTS, + intsts & (DW_MMC_INT_ERROR | DW_MMC_INT_CMD_DONE)); + + mmc_err = dw_mmc_cmd_read_response(sc, cmd, intsts); + if (mmc_err != 0) { + return mmc_err; + } + + if (data != NULL) { + intsts = dw_mmc_cmd_data_transfer(sc, data, done_bytes, use_dma); + mmc_err = dw_mmc_cmd_data_finish(sc, intsts); + } + + return mmc_err; +} + +static size_t +dw_mmc_fill_fifo(struct dw_mmc_softc *sc, struct mmc_data *data) +{ + uint32_t *data32 = data->data; + size_t count_bytes = data->len; + size_t count_words = 0; + size_t dangling_bytes; + size_t i; + + if (count_bytes >= DW_MMC_FIFO_DEPTH * DW_MMC_FIFO_WIDTH) { + count_words = DW_MMC_FIFO_DEPTH; + dangling_bytes = 0; + } else { + count_words = count_bytes / DW_MMC_FIFO_WIDTH; + dangling_bytes = count_bytes % DW_MMC_FIFO_WIDTH; + } + + for (i = 0; i < count_words; ++i) { + WR4(sc, DW_MMC_DATA, data32[i]); + } + + if (dangling_bytes) { + uint32_t tmp = 0; + + memcpy(&tmp, &data32[i], dangling_bytes); + WR4(sc, DW_MMC_DATA, tmp); + } + + return count_words * DW_MMC_FIFO_WIDTH + dangling_bytes; +} + +static bool dw_mmc_dma_can_use(const struct mmc_data *data) +{ + uintptr_t cache_line = 32; + + return data->len >= cache_line + && ((data->len | (uintptr_t) data->data) & (cache_line - 1)) == 0; +} + +static void +dw_mmc_dma_setup(struct dw_mmc_softc *sc, struct mmc_data *data) +{ + volatile struct dw_mmc_des *des = sc->des; + uint32_t buf = (uint32_t) data->data; + size_t count_bytes = data->len; + uint32_t fs = DW_MMC_DES0_FS; + size_t s = 2 * DW_MMC_DES1_MAX_BS; + size_t n = (count_bytes + s - 1) / s; + size_t m = count_bytes % s; + size_t i; + + for (i = 0; i < n - 1; ++i) { + des[i].des1 = DW_MMC_DES1_BS1(DW_MMC_DES1_MAX_BS) + | DW_MMC_DES1_BS2(DW_MMC_DES1_MAX_BS); + des[i].des2 = buf; + buf += DW_MMC_DES1_MAX_BS; + des[i].des3 = buf; + buf += DW_MMC_DES1_MAX_BS; + des[i].des0 = DW_MMC_DES0_OWN | fs; + fs = 0; + } + + if (m > DW_MMC_DES1_MAX_BS) { + des[i].des1 = DW_MMC_DES1_BS1(DW_MMC_DES1_MAX_BS) + | DW_MMC_DES1_BS2(m - DW_MMC_DES1_MAX_BS); + des[i].des2 = buf; + buf += DW_MMC_DES1_MAX_BS; + des[i].des3 = buf; + } else { + des[i].des1 = DW_MMC_DES1_BS1(m); + des[i].des2 = buf; + des[i].des3 = 0; + } + + des[i].des0 = DW_MMC_DES0_OWN | DW_MMC_DES0_ER | fs | DW_MMC_DES0_LD; + +#ifdef __arm__ + _ARM_Data_synchronization_barrier(); +#else + /* TODO */ +#endif +} + + +static void +dw_mmc_cmd_do(struct dw_mmc_softc *sc, struct mmc_request *req, + struct mmc_command *cmd) +{ + size_t done_bytes = 0; + bool use_dma = false; + struct mmc_data *data; + uint32_t cmdr; + + data = cmd->data; + cmdr = cmd->opcode; + + if (cmd->opcode == MMC_STOP_TRANSMISSION) { + cmdr |= DW_MMC_CMD_SEND_STOP; + } else { + cmdr |= DW_MMC_CMD_PRV_DATA_WAIT; + } + + cmdr |= sc->cmdr_flags; + sc->cmdr_flags &= ~DW_MMC_CMD_SEND_INIT; + + if (MMC_RSP(cmd->flags) != MMC_RSP_NONE) { + cmdr |= DW_MMC_CMD_RESP_EXP; + + if ((cmd->flags & MMC_RSP_136) != 0) { + cmdr |= DW_MMC_CMD_RESP_LONG; + } + } + + if ((cmd->flags & MMC_RSP_CRC) != 0) { + cmdr |= DW_MMC_CMD_RESP_CRC; + } + + if (data != NULL) { + size_t count_bytes = data->len; + uint32_t ctrl; + int mmc_err; + + cmdr |= DW_MMC_CMD_DATA_EXP; + + if ((data->flags & MMC_DATA_MULTI) != 0) { + cmdr |= DW_MMC_CMD_SEND_STOP; + } + + mmc_err = dw_mmc_fifo_and_dma_reset(sc); + if (mmc_err != 0) { + cmd->error = mmc_err; + return; + } + + use_dma = dw_mmc_dma_can_use(data); + + ctrl = RD4(sc, DW_MMC_CTRL); + + if (use_dma) { + ctrl |= DW_MMC_CTRL_DMA_ENABLE; + } else { + ctrl &= ~DW_MMC_CTRL_DMA_ENABLE; + } + + WR4(sc, DW_MMC_CTRL, ctrl); + WR4(sc, DW_MMC_BLKSIZ, MIN(count_bytes, MMC_SECTOR_SIZE)); + WR4(sc, DW_MMC_BYTCNT, count_bytes); + + if ((data->flags & MMC_DATA_WRITE) != 0) { + cmdr |= DW_MMC_CMD_DATA_WR; + + if (use_dma) { + rtems_cache_flush_multiple_data_lines(data->data, + count_bytes); + } else { + done_bytes = dw_mmc_fill_fifo(sc, data); + } + } else if (use_dma) { + rtems_cache_invalidate_multiple_data_lines(data->data, + count_bytes); + } + + if (use_dma) { + if (count_bytes > DW_MMC_MAX_DMA_TRANSFER_BYTES) { + cmd->error = MMC_ERR_INVALID; + return; + } + + dw_mmc_dma_setup(sc, data); + } + } + + dw_mmc_cmd_start(sc, cmdr, cmd->arg); + + if (use_dma) { + WR4(sc, DW_MMC_PLDMND, 0); + } + + cmd->error = dw_mmc_cmd_done(sc, cmd, data, done_bytes, use_dma); +} + +static int +dw_mmc_request(device_t brdev, device_t reqdev, struct mmc_request *req) +{ + struct dw_mmc_softc *sc = device_get_softc(brdev); + + DW_MMC_LOCK(sc); + dw_mmc_cmd_do(sc, req, req->cmd); + DW_MMC_UNLOCK(sc); + + (*req->done)(req); + + return (0); +} + +static int +dw_mmc_get_ro(device_t brdev, device_t reqdev) +{ + return (0); +} + +static int +dw_mmc_acquire_host(device_t brdev, device_t reqdev) +{ + struct dw_mmc_softc *sc = device_get_softc(brdev); + + DW_MMC_BUS_LOCK(sc); + + return (0); +} + +static int +dw_mmc_release_host(device_t brdev, device_t reqdev) +{ + struct dw_mmc_softc *sc = device_get_softc(brdev); + + DW_MMC_BUS_UNLOCK(sc); + + return (0); +} + +static void +dw_mmc_intr(void *arg) +{ + struct dw_mmc_softc *sc = (struct dw_mmc_softc *) arg; + rtems_status_code rs; + + WR4(sc, DW_MMC_INTMASK, 0); + + rs = rtems_event_transient_send(sc->task_id); + BSD_ASSERT(rs == RTEMS_SUCCESSFUL); +} + +static int +dw_mmc_read_ivar(device_t bus, device_t child, int which, uintptr_t *result) +{ + struct dw_mmc_softc *sc = device_get_softc(bus); + + switch (which) { + default: + return (EINVAL); + case MMCBR_IVAR_BUS_MODE: + *(int *)result = sc->host.ios.bus_mode; + break; + case MMCBR_IVAR_BUS_WIDTH: + *(int *)result = sc->host.ios.bus_width; + break; + case MMCBR_IVAR_CHIP_SELECT: + *(int *)result = sc->host.ios.chip_select; + break; + case MMCBR_IVAR_CLOCK: + *(int *)result = sc->host.ios.clock; + break; + case MMCBR_IVAR_F_MIN: + *(int *)result = sc->host.f_min; + break; + case MMCBR_IVAR_F_MAX: + *(int *)result = sc->host.f_max; + break; + case MMCBR_IVAR_HOST_OCR: + *(int *)result = sc->host.host_ocr; + break; + case MMCBR_IVAR_MODE: + *(int *)result = sc->host.mode; + break; + case MMCBR_IVAR_OCR: + *(int *)result = sc->host.ocr; + break; + case MMCBR_IVAR_POWER_MODE: + *(int *)result = sc->host.ios.power_mode; + break; + case MMCBR_IVAR_VDD: + *(int *)result = sc->host.ios.vdd; + break; + case MMCBR_IVAR_CAPS: + *(int *)result = sc->host.caps; + break; + case MMCBR_IVAR_MAX_DATA: + *(int *)result = 1; + break; + } + return (0); +} + +static int +dw_mmc_write_ivar(device_t bus, device_t child, int which, uintptr_t value) +{ + struct dw_mmc_softc *sc = device_get_softc(bus); + + switch (which) { + default: + return (EINVAL); + case MMCBR_IVAR_BUS_MODE: + sc->host.ios.bus_mode = value; + break; + case MMCBR_IVAR_BUS_WIDTH: + sc->host.ios.bus_width = value; + break; + case MMCBR_IVAR_CHIP_SELECT: + sc->host.ios.chip_select = value; + break; + case MMCBR_IVAR_CLOCK: + sc->host.ios.clock = value; + break; + case MMCBR_IVAR_MODE: + sc->host.mode = value; + break; + case MMCBR_IVAR_OCR: + sc->host.ocr = value; + break; + case MMCBR_IVAR_POWER_MODE: + sc->host.ios.power_mode = value; + break; + case MMCBR_IVAR_VDD: + sc->host.ios.vdd = value; + break; + /* These are read-only */ + 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 dw_mmc_methods[] = { + /* device_if */ + DEVMETHOD(device_probe, dw_mmc_probe), + DEVMETHOD(device_attach, dw_mmc_attach), + DEVMETHOD(device_detach, dw_mmc_detach), + + /* Bus interface */ + DEVMETHOD(bus_read_ivar, dw_mmc_read_ivar), + DEVMETHOD(bus_write_ivar, dw_mmc_write_ivar), + + /* mmcbr_if */ + DEVMETHOD(mmcbr_update_ios, dw_mmc_update_ios), + DEVMETHOD(mmcbr_request, dw_mmc_request), + DEVMETHOD(mmcbr_get_ro, dw_mmc_get_ro), + DEVMETHOD(mmcbr_acquire_host, dw_mmc_acquire_host), + DEVMETHOD(mmcbr_release_host, dw_mmc_release_host), + + DEVMETHOD_END +}; + +static driver_t dw_mmc_driver = { + "dw_mmc", + dw_mmc_methods, + sizeof(struct dw_mmc_softc) +}; + +static devclass_t dw_mmc_devclass; + +DRIVER_MODULE(dw_mmc, nexus, dw_mmc_driver, dw_mmc_devclass, NULL, NULL); diff --git a/rtemsbsd/sys/dev/dw_mmc/dw_mmcreg.h b/rtemsbsd/sys/dev/dw_mmc/dw_mmcreg.h new file mode 100644 index 00000000..d6936a56 --- /dev/null +++ b/rtemsbsd/sys/dev/dw_mmc/dw_mmcreg.h @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2014 embedded brains GmbH. All rights reserved. + * + * embedded brains GmbH + * Dornierstr. 4 + * 82178 Puchheim + * Germany + * + * + * 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 COPYRIGHT HOLDERS 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 COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DW_MMC_DW_MMCREG_H +#define DW_MMC_DW_MMCREG_H + +#define DW_MMC_CTRL 0x000 +#define DW_MMC_PWREN 0x004 +#define DW_MMC_CLKDIV 0x008 +#define DW_MMC_CLKSRC 0x00c +#define DW_MMC_CLKENA 0x010 +#define DW_MMC_TMOUT 0x014 +#define DW_MMC_CTYPE 0x018 +#define DW_MMC_BLKSIZ 0x01c +#define DW_MMC_BYTCNT 0x020 +#define DW_MMC_INTMASK 0x024 +#define DW_MMC_CMDARG 0x028 +#define DW_MMC_CMD 0x02c +#define DW_MMC_RESP0 0x030 +#define DW_MMC_RESP1 0x034 +#define DW_MMC_RESP2 0x038 +#define DW_MMC_RESP3 0x03c +#define DW_MMC_MINTSTS 0x040 +#define DW_MMC_RINTSTS 0x044 +#define DW_MMC_STATUS 0x048 +#define DW_MMC_FIFOTH 0x04c +#define DW_MMC_CDETECT 0x050 +#define DW_MMC_WRTPRT 0x054 +#define DW_MMC_GPIO 0x058 +#define DW_MMC_TCBCNT 0x05c +#define DW_MMC_TBBCNT 0x060 +#define DW_MMC_DEBNCE 0x064 +#define DW_MMC_USRID 0x068 +#define DW_MMC_VERID 0x06c +#define DW_MMC_HCON 0x070 +#define DW_MMC_UHS_REG 0x074 +#define DW_MMC_RST_N 0x078 +#define DW_MMC_BMOD 0x080 +#define DW_MMC_PLDMND 0x084 +#define DW_MMC_DBADDR 0x088 +#define DW_MMC_IDSTS 0x08c +#define DW_MMC_IDINTEN 0x090 +#define DW_MMC_DSCADDR 0x094 +#define DW_MMC_BUFADDR 0x098 +#define DW_MMC_DATA 0x200 + +/* Control Register */ +#define DW_MMC_CTRL_DMA_ENABLE (1u << 25) +#define DW_MMC_CTRL_CEATA_INT_EN (1u << 11) +#define DW_MMC_CTRL_SEND_AS_CCSD (1u << 10) +#define DW_MMC_CTRL_SEND_CCSD (1u << 9) +#define DW_MMC_CTRL_ABRT_READ_DATA (1u << 8) +#define DW_MMC_CTRL_SEND_IRQ_RESP (1u << 7) +#define DW_MMC_CTRL_READ_WAIT (1u << 6) +#define DW_MMC_CTRL_INT_ENABLE (1u << 4) +#define DW_MMC_CTRL_DMA_RESET (1u << 2) +#define DW_MMC_CTRL_FIFO_RESET (1u << 1) +#define DW_MMC_CTRL_RESET (1u << 0) + +/* Power Enable Register */ +#define DW_MMC_PWREN_ENABLE (1u << 0) + +/* Clock Enable Register */ +#define DW_MMC_CLKEN_LOW_PWR (1u << 16) +#define DW_MMC_CLKEN_ENABLE (1u << 0) + +/* Timeout Register */ +#define DW_MMC_TMOUT_DATA(x) ((x) << 8) +#define DW_MMC_TMOUT_DATA_MSK 0xffffff00 +#define DW_MMC_TMOUT_RESP(x) ((x) & 0xFF) +#define DW_MMC_TMOUT_RESP_MSK 0xff + +/* Card Type Register */ +#define DW_MMC_CTYPE_8BIT (1u << 16) +#define DW_MMC_CTYPE_4BIT (1u << 0) +#define DW_MMC_CTYPE_1BIT 0 + +/* Interrupt Status and Interrupt Mask Register */ +#define DW_MMC_INT_SDIO(x) (1u << (16 + (x))) +#define DW_MMC_INT_EBE (1u << 15) +#define DW_MMC_INT_ACD (1u << 14) +#define DW_MMC_INT_SBE (1u << 13) +#define DW_MMC_INT_HLE (1u << 12) +#define DW_MMC_INT_FRUN (1u << 11) +#define DW_MMC_INT_HTO (1u << 10) +#define DW_MMC_INT_DRTO (1u << 9) +#define DW_MMC_INT_RTO (1u << 8) +#define DW_MMC_INT_DCRC (1u << 7) +#define DW_MMC_INT_RCRC (1u << 6) +#define DW_MMC_INT_RXDR (1u << 5) +#define DW_MMC_INT_TXDR (1u << 4) +#define DW_MMC_INT_DTO (1u << 3) +#define DW_MMC_INT_CMD_DONE (1u << 2) +#define DW_MMC_INT_RE (1u << 1) +#define DW_MMC_INT_CD (1u << 0) +#define DW_MMC_INT_ERROR (DW_MMC_INT_RE | DW_MMC_INT_RCRC | DW_MMC_INT_DCRC \ + | DW_MMC_INT_DRTO | DW_MMC_INT_RTO | DW_MMC_INT_EBE) + +/* Command Register */ +#define DW_MMC_CMD_START (1u << 31) +#define DW_MMC_CMD_USE_HOLD_REG (1u << 29) +#define DW_MMC_CMD_VOLT_SWITCH (1u << 28) +#define DW_MMC_CMD_BOOT_MODE (1u << 27) +#define DW_MMC_CMD_DISABLE_BOOT (1u << 26) +#define DW_MMC_CMD_EXP_BOOT_ACK (1u << 25) +#define DW_MMC_CMD_ENABLE_BOOT (1u << 24) +#define DW_MMC_CMD_CCS_EXP (1u << 23) +#define DW_MMC_CMD_CEATA_RD (1u << 22) +#define DW_MMC_CMD_UPDATE_CLK (1u << 21) +#define DW_MMC_CMD_SEND_INIT (1u << 15) +#define DW_MMC_CMD_STOP_ABRT (1u << 14) +#define DW_MMC_CMD_PRV_DATA_WAIT (1u << 13) +#define DW_MMC_CMD_SEND_STOP (1u << 12) +#define DW_MMC_CMD_STREAM_MODE (1u << 11) +#define DW_MMC_CMD_DATA_WR (1u << 10) +#define DW_MMC_CMD_DATA_EXP (1u << 9) +#define DW_MMC_CMD_RESP_CRC (1u << 8) +#define DW_MMC_CMD_RESP_LONG (1u << 7) +#define DW_MMC_CMD_RESP_EXP (1u << 6) +#define DW_MMC_CMD_INDEX(x) ((x) & 0x1f) + +/* Status Register */ +#define DW_MMC_STATUS_GET_FIFO_CNT(x) (((x) >> 17) & 0x1fff) +#define DW_MMC_STATUS_GET_RESP_IDX(x) (((x) >> 11) & 0x3f) +#define DW_MMC_STATUS_DS_MC_BUSY (1u << 10) +#define DW_MMC_STATUS_CARD_DATA_BUSY (1u << 9) +#define DW_MMC_STATUS_CARD_PRESENT (1u << 8) +#define DW_MMC_STATUS_GET_FSM_STATE(x) (((x) >> 4) & 0xf) +#define DW_MMC_STATUS_FIFO_FULL (1u << 3) +#define DW_MMC_STATUS_FIFO_EMPTY (1u << 2) +#define DW_MMC_STATUS_FIFO_TX_WM (1u << 1) +#define DW_MMC_STATUS_FIFO_RX_WM (1u << 0) + +/* DMA and FIFO Control Register */ +#define DW_MMC_FIFOTH_BSZ(x) ((x) << 28) +#define DW_MMC_FIFOTH_BSZ_MSK DW_MMC_FIFOTH_BSZ(0x7) +#define DW_MMC_FIFOTH_RX_WMARK(x) ((x) << 16) +#define DW_MMC_FIFOTH_RX_WMARK_MSK DW_MMC_FIFOTH_RX_WMARK(0xfff) +#define DW_MMC_FIFOTH_TX_WMARK(x) ((x) << 0) +#define DW_MMC_FIFOTH_TX_WMARK_MSK DW_MMC_FIFOTH_TX_WMARK(0xfff) + +/* Card Detect Register */ +#define DW_MMC_CDETECT_NOT_DETECTED (1u << 0) + +/* Write Protect Register */ +#define DW_MMC_WRTPRT_ENABLED (1u << 0) + +/* Hardware Reset Register */ +#define DW_MMC_RST_N_ACTIVE_MODE (1u << 0) + +/* Bus Mode Register */ +#define DW_MMC_BMOD_DE (1u << 7) +#define DW_MMC_BMOD_FB (1u << 1) +#define DW_MMC_BMOD_SWR (1u << 0) + +/* Internal DMAC Status and Interrupt DMAC Interrupt Enable Register */ +#define DW_MMC_IDMAC_INT_GET_FSM(x) (((x) >> 13) & 0xf) +#define DW_MMC_IDMAC_INT_GET_EB(x) (((x) >> 10) & 0x7) +#define DW_MMC_IDMAC_INT_AIS (1u << 9) +#define DW_MMC_IDMAC_INT_NIS (1u << 8) +#define DW_MMC_IDMAC_INT_CES (1u << 5) +#define DW_MMC_IDMAC_INT_DU (1u << 4) +#define DW_MMC_IDMAC_INT_FBE (1u << 2) +#define DW_MMC_IDMAC_INT_RI (1u << 1) +#define DW_MMC_IDMAC_INT_TI (1u << 0) + +/* Internal DMA descriptor */ + +struct dw_mmc_des { + uint32_t des0; + uint32_t des1; + uint32_t des2; + uint32_t des3; +}; + +#define DW_MMC_DES0_OWN (1u << 31) +#define DW_MMC_DES0_CES (1u << 30) +#define DW_MMC_DES0_ER (1u << 5) +#define DW_MMC_DES0_CH (1u << 4) +#define DW_MMC_DES0_FS (1u << 3) +#define DW_MMC_DES0_LD (1u << 2) +#define DW_MMC_DES0_DIC (1u << 1) + +#define DW_MMC_DES1_BS2(x) ((x) << 13) +#define DW_MMC_DES1_BS1(x) ((x) << 0) +#define DW_MMC_DES1_MAX_BS 4096 + +/* FIFO dimensions */ +#define DW_MMC_FIFO_DEPTH 1024 +#define DW_MMC_FIFO_WIDTH 4 + +#endif /* DW_MMC_DW_MMCREG_H */ diff --git a/testsuite/include/rtems/bsd/test/default-network-init.h b/testsuite/include/rtems/bsd/test/default-network-init.h index 711cb111..52928a6c 100644 --- a/testsuite/include/rtems/bsd/test/default-network-init.h +++ b/testsuite/include/rtems/bsd/test/default-network-init.h @@ -287,6 +287,7 @@ SYSINIT_NEED_NET_PF_UNIX; #define CONFIGURE_APPLICATION_NEEDS_CONSOLE_DRIVER #define CONFIGURE_APPLICATION_NEEDS_STUB_DRIVER #define CONFIGURE_APPLICATION_NEEDS_ZERO_DRIVER +#define CONFIGURE_APPLICATION_NEEDS_LIBBLOCK #define CONFIGURE_USE_IMFS_AS_BASE_FILESYSTEM