diff options
author | Uwe Stuehler <uwe@cvs.openbsd.org> | 2007-03-18 20:53:11 +0000 |
---|---|---|
committer | Uwe Stuehler <uwe@cvs.openbsd.org> | 2007-03-18 20:53:11 +0000 |
commit | 9a1bf0c4b0a09c9e7a03e964c8ee6c3608fea836 (patch) | |
tree | c189c1e443ffac2fac8d4100f00bdac45d7dbd09 /sys | |
parent | dbae9d9f778079bf5b61ef91c0bb7361e2a5f1e4 (diff) |
Support the PXA27x SD/SDIO/MMC controller on Zaurus
We use the suggested workaround for the problem E40 in the PXA27x errata
sheet. Unfortunately this limits the bus speed to 9.75Mhz.
Diffstat (limited to 'sys')
-rw-r--r-- | sys/arch/arm/xscale/files.pxa2x0 | 6 | ||||
-rw-r--r-- | sys/arch/arm/xscale/pxa2x0_mmc.c | 675 | ||||
-rw-r--r-- | sys/arch/arm/xscale/pxa2x0reg.h | 17 | ||||
-rw-r--r-- | sys/arch/arm/xscale/pxammcvar.h | 42 | ||||
-rw-r--r-- | sys/arch/zaurus/conf/GENERIC | 6 | ||||
-rw-r--r-- | sys/arch/zaurus/conf/files.zaurus | 11 | ||||
-rw-r--r-- | sys/arch/zaurus/dev/scoop_mmc.c | 78 | ||||
-rw-r--r-- | sys/dev/sdmmc/sdmmcvar.h | 9 |
8 files changed, 832 insertions, 12 deletions
diff --git a/sys/arch/arm/xscale/files.pxa2x0 b/sys/arch/arm/xscale/files.pxa2x0 index ec2b493eb36..64a50c32aff 100644 --- a/sys/arch/arm/xscale/files.pxa2x0 +++ b/sys/arch/arm/xscale/files.pxa2x0 @@ -1,4 +1,4 @@ -# $OpenBSD: files.pxa2x0,v 1.19 2006/11/25 18:10:29 uwe Exp $ +# $OpenBSD: files.pxa2x0,v 1.20 2007/03/18 20:53:10 uwe Exp $ # $NetBSD: files.pxa2x0,v 1.6 2004/05/01 19:09:14 thorpej Exp $ # # Configuration info for Intel PXA2[51]0 CPU support @@ -79,3 +79,7 @@ file arch/arm/xscale/pxa2x0_pcic.c pxapcic # XXX this is a hack to use dev/pcmcia without fdc.c device fdc + +# MMC/SD/SDIO controller +device pxammc: sdmmcbus +file arch/arm/xscale/pxa2x0_mmc.c pxammc diff --git a/sys/arch/arm/xscale/pxa2x0_mmc.c b/sys/arch/arm/xscale/pxa2x0_mmc.c new file mode 100644 index 00000000000..3adf915cd41 --- /dev/null +++ b/sys/arch/arm/xscale/pxa2x0_mmc.c @@ -0,0 +1,675 @@ +/* $OpenBSD: pxa2x0_mmc.c,v 1.1 2007/03/18 20:53:10 uwe Exp $ */ + +/* + * Copyright (c) 2007 Uwe Stuehler <uwe@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * MMC/SD/SDIO controller driver for Intel PXA27x processors + * + * Power management is beond control of the processor's SD/SDIO/MMC + * block, so this driver depends on the attachment driver to provide + * us with some callback functions via the "tag" member in our softc. + * Bus power management calls are then dispatched to the attachment + * driver. + */ + +#include <sys/param.h> +#include <sys/device.h> +#include <sys/kernel.h> +#include <sys/proc.h> +#include <sys/systm.h> + +#include <machine/bus.h> + +#include <arch/arm/xscale/pxa2x0_gpio.h> +#include <arch/arm/xscale/pxa2x0reg.h> +#include <arch/arm/xscale/pxa2x0var.h> +#include <arch/arm/xscale/pxammcvar.h> + +#include <dev/sdmmc/sdmmcchip.h> +#include <dev/sdmmc/sdmmcreg.h> +#include <dev/sdmmc/sdmmcvar.h> + +/* GPIO pins */ +#define PXAMMC_CARD_DETECT 9 /* XXX zaurus-specific */ +#define PXAMMC_MMCLK 32 +#define PXAMMC_MMCMD 112 +#define PXAMMC_MMDAT0 92 +#define PXAMMC_MMDAT1 109 +#define PXAMMC_MMDAT2 110 +#define PXAMMC_MMDAT3 111 + +int pxammc_host_reset(sdmmc_chipset_handle_t); +u_int32_t pxammc_host_ocr(sdmmc_chipset_handle_t); +int pxammc_host_maxblklen(sdmmc_chipset_handle_t); +int pxammc_card_detect(sdmmc_chipset_handle_t); +int pxammc_bus_power(sdmmc_chipset_handle_t, u_int32_t); +int pxammc_bus_clock(sdmmc_chipset_handle_t, int); +void pxammc_exec_command(sdmmc_chipset_handle_t, struct sdmmc_command *); +void pxammc_clock_stop(struct pxammc_softc *); +void pxammc_clock_start(struct pxammc_softc *); +int pxammc_card_intr(void *); +int pxammc_intr(void *); +void pxammc_intr_cmd(struct pxammc_softc *); +void pxammc_intr_data(struct pxammc_softc *); +void pxammc_intr_done(struct pxammc_softc *); + +#define CSR_READ_1(sc, reg) \ + bus_space_read_1((sc)->sc_iot, (sc)->sc_ioh, (reg)) +#define CSR_WRITE_1(sc, reg, val) \ + bus_space_write_1((sc)->sc_iot, (sc)->sc_ioh, (reg), (val)) +#define CSR_READ_4(sc, reg) \ + bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg)) +#define CSR_WRITE_4(sc, reg, val) \ + bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val)) +#define CSR_SET_4(sc, reg, bits) \ + CSR_WRITE_4((sc), (reg), CSR_READ_4((sc), (reg)) | (bits)) +#define CSR_CLR_4(sc, reg, bits) \ + CSR_WRITE_4((sc), (reg), CSR_READ_4((sc), (reg)) & ~(bits)) + +struct sdmmc_chip_functions pxammc_functions = { + /* host controller reset */ + pxammc_host_reset, + /* host controller capabilities */ + pxammc_host_ocr, + pxammc_host_maxblklen, + /* card detection */ + pxammc_card_detect, + /* bus power and clock frequency */ + pxammc_bus_power, + pxammc_bus_clock, + /* command execution */ + pxammc_exec_command +}; + +struct cfdriver pxammc_cd = { + NULL, "pxammc", DV_DULL +}; + +#define SDMMC_DEBUG +#ifdef SDMMC_DEBUG +int sdhcdebug = 0; /* XXX must be named sdhcdebug for sdmmc.c */ +#define DPRINTF(n,s) do { if ((n) <= sdhcdebug) printf s; } while (0) +#else +#define DPRINTF(n,s) do {} while (0) +#endif + +int +pxammc_match(void) +{ + return (cputype & ~CPU_ID_XSCALE_COREREV_MASK) == CPU_ID_PXA27X; +} + +void +pxammc_attach(struct pxammc_softc *sc, void *aux) +{ + struct pxaip_attach_args *pxa = aux; + struct sdmmcbus_attach_args saa; + int s; + + /* Enable the clocks to the MMC controller. */ + pxa2x0_clkman_config(CKEN_MMC, 1); + + sc->sc_iot = pxa->pxa_sa.sa_iot; + if (bus_space_map(sc->sc_iot, PXA2X0_MMC_BASE, PXA2X0_MMC_SIZE, 0, + &sc->sc_ioh) != 0) { + printf(": can't map regs\n"); + goto fail; + } + + /* + * Establish the card detection and MMC interrupt handlers and + * mask all interrupts until we are prepared to handle them. + */ + s = splsdmmc(); + + pxa2x0_gpio_set_function(PXAMMC_CARD_DETECT, GPIO_IN); + sc->sc_card_ih = pxa2x0_gpio_intr_establish(PXAMMC_CARD_DETECT, + IST_EDGE_BOTH, IPL_SDMMC, pxammc_card_intr, sc, "mmccd"); + if (sc->sc_card_ih == NULL) { + splx(s); + printf(": can't establish card interrupt\n"); + goto fail; + } + pxa2x0_gpio_intr_mask(sc->sc_card_ih); + + sc->sc_ih = pxa2x0_intr_establish(PXA2X0_INT_MMC, IPL_SDMMC, + pxammc_intr, sc, sc->sc_dev.dv_xname); + if (sc->sc_ih == NULL) { + splx(s); + printf(": can't establish MMC interrupt\n"); + goto fail; + } + CSR_WRITE_4(sc, MMC_I_MASK, 0xffffffff); + + splx(s); + + printf(": MMC/SD/SDIO controller\n"); + + /* + * Configure the GPIO pins. In SD/MMC mode, all pins except + * MMCLK are bidirectional and the direction is controlled in + * hardware without our assistence. + */ + pxa2x0_gpio_set_function(PXAMMC_MMCLK, GPIO_ALT_FN_2_OUT); + pxa2x0_gpio_set_function(PXAMMC_MMCMD, GPIO_ALT_FN_1_IN); + pxa2x0_gpio_set_function(PXAMMC_MMDAT0, GPIO_ALT_FN_1_IN); + pxa2x0_gpio_set_function(PXAMMC_MMDAT1, GPIO_ALT_FN_1_IN); + pxa2x0_gpio_set_function(PXAMMC_MMDAT2, GPIO_ALT_FN_1_IN); + pxa2x0_gpio_set_function(PXAMMC_MMDAT3, GPIO_ALT_FN_1_IN); + + /* + * Reset the host controller and unmask normal interrupts. + */ + (void)pxammc_host_reset(sc); + + /* + * Attach the generic sdmmc bus driver. + */ + bzero(&saa, sizeof saa); + saa.saa_busname = "sdmmc"; + saa.sct = &pxammc_functions; + saa.sch = sc; + + sc->sc_sdmmc = config_found(&sc->sc_dev, &saa, NULL); + if (sc->sc_sdmmc == NULL) { + printf("%s: can't attach bus\n", sc->sc_dev.dv_xname); + goto fail; + } + + /* Enable card detection interrupt. */ + pxa2x0_gpio_intr_unmask(sc->sc_card_ih); + return; + +fail: + if (sc->sc_ih != NULL) { + pxa2x0_intr_disestablish(sc->sc_ih); + sc->sc_ih = NULL; + } + if (sc->sc_card_ih != NULL) { + pxa2x0_gpio_intr_disestablish(sc->sc_card_ih); + sc->sc_card_ih = NULL; + } + if (sc->sc_ioh != NULL) { + bus_space_unmap(sc->sc_iot, sc->sc_ioh, PXA2X0_MMC_SIZE); + sc->sc_ioh = NULL; + } + pxa2x0_clkman_config(CKEN_MMC, 0); +} + +int +pxammc_host_reset(sdmmc_chipset_handle_t sch) +{ + struct pxammc_softc *sc = sch; + int s = splsdmmc(); + + /* Make sure to initialize the card before the next command. */ + CLR(sc->sc_flags, PMF_CARD_INITED); + + /* Disable SPI mode (we don't support SPI). */ + CSR_WRITE_4(sc, MMC_SPI, 0); + + /* Set response timeout to maximum. */ + CSR_WRITE_4(sc, MMC_RESTO, 0x7f); + + /* Enable all interrupts. */ + CSR_WRITE_4(sc, MMC_I_MASK, 0); + + splx(s); + return 0; +} + +int +pxammc_host_maxblklen(sdmmc_chipset_handle_t sch) +{ + return 2048; +} + +u_int32_t +pxammc_host_ocr(sdmmc_chipset_handle_t sch) +{ + struct pxammc_softc *sc = sch; + + if (sc->tag.get_ocr != NULL) + return sc->tag.get_ocr(sc->tag.cookie); + + DPRINTF(0,("%s: driver lacks get_ocr() function\n", + sc->sc_dev.dv_xname)); + return ENXIO; +} + +int +pxammc_card_detect(sdmmc_chipset_handle_t sch) +{ + return !pxa2x0_gpio_get_bit(PXAMMC_CARD_DETECT); +} + +int +pxammc_bus_power(sdmmc_chipset_handle_t sch, u_int32_t ocr) +{ + struct pxammc_softc *sc = sch; + + /* + * Bus power management is beond control of the SD/SDIO/MMC + * block of the PXA2xx processors, so we have to hand this + * task off to the attachment driver. + */ + if (sc->tag.set_power != NULL) + return sc->tag.set_power(sc->tag.cookie, ocr); + + DPRINTF(0,("%s: driver lacks set_power() function\n", + sc->sc_dev.dv_xname)); + return ENXIO; +} + +int +pxammc_bus_clock(sdmmc_chipset_handle_t sch, int freq) +{ + struct pxammc_softc *sc = sch; + int actfreq = 19500; /* KHz */ + int div = 0; + int s; + + s = splsdmmc(); + + /* Stop the clock and wait for the interrupt. */ + pxammc_clock_stop(sc); + + /* Just stop the clock. */ + if (freq == 0) { + splx(s); + return 0; + } + + /* + * PXA27x Errata... + * + * <snip> + * E40. SDIO: SDIO Devices Not Working at 19.5 Mbps + * + * SD/SDIO controller can only support up to 9.75 Mbps data + * transfer rate for SDIO card. + * </snip> + * + * If we don't limit the frequency, CRC errors will be + * reported by the controller after we set the bus speed. + * XXX slow down incrementally. + */ + if (freq > 9750) + freq = 9750; + + /* + * Pick the smallest divider that produces a frequency not + * more than `freq' KHz. + */ + while (div < 7) { + if (actfreq <= freq) + break; + actfreq /= 2; + div++; + } + if (div == 7) { + splx(s); + printf("%s: unsupported bus frequency of %d KHz\n", + sc->sc_dev.dv_xname, freq); + return -1; + } + + DPRINTF(1,("pxammc_bus_clock freq=%d actfreq=%d div=%d\n", + freq, actfreq, div)); + sc->sc_clkdiv = div; + pxammc_clock_start(sc); + splx(s); + return 0; +} + +void +pxammc_exec_command(sdmmc_chipset_handle_t sch, + struct sdmmc_command *cmd) +{ + struct pxammc_softc *sc = sch; + u_int32_t cmdat; + int timo; + int s; + + DPRINTF(1,("%s: cmd %u arg=%#x data=%#x dlen=%d flags=%#x " + "proc=\"%s\"\n", sc->sc_dev.dv_xname, cmd->c_opcode, + cmd->c_arg, cmd->c_data, cmd->c_datalen, cmd->c_flags, + curproc ? curproc->p_comm : "")); + + s = splsdmmc(); + + /* Stop the bus clock (MMCLK). [15.8.3] */ + pxammc_clock_stop(sc); + + /* Set the command and argument. */ + CSR_WRITE_4(sc, MMC_CMD, cmd->c_opcode); + CSR_WRITE_4(sc, MMC_ARGH, (cmd->c_arg >> 16) & 0xffff); + CSR_WRITE_4(sc, MMC_ARGL, cmd->c_arg & 0xffff); + + /* Set response characteristics for this command. */ + if (!ISSET(cmd->c_flags, SCF_RSP_PRESENT)) + cmdat = CMDAT_RESPONSE_FORMAT_NO; + else if (ISSET(cmd->c_flags, SCF_RSP_136)) + cmdat = CMDAT_RESPONSE_FORMAT_R2; + else if (!ISSET(cmd->c_flags, SCF_RSP_CRC)) + cmdat = CMDAT_RESPONSE_FORMAT_R3; + else + cmdat = CMDAT_RESPONSE_FORMAT_R1; + + if (ISSET(cmd->c_flags, SCF_RSP_BSY)) + cmdat |= CMDAT_BUSY; + + if (!ISSET(cmd->c_flags, SCF_CMD_READ)) + cmdat |= CMDAT_WRITE; + + /* Fragment the data into proper blocks. */ + if (cmd->c_datalen > 0) { + int blklen = MIN(cmd->c_datalen, cmd->c_blklen); + int numblk = cmd->c_datalen / blklen; + + if (cmd->c_datalen % blklen > 0) { + /* XXX: Split this command. (1.7.4) */ + printf("%s: data not a multiple of %d bytes\n", + sc->sc_dev.dv_xname, blklen); + cmd->c_error = EINVAL; + splx(s); + return; + } + + CSR_WRITE_4(sc, MMC_BLKLEN, blklen); + CSR_WRITE_4(sc, MMC_NUMBLK, numblk); + + /* Enable data interrupts. */ + CSR_CLR_4(sc, MMC_I_MASK, MMC_I_DATA_TRAN_DONE | + MMC_I_RXFIFO_RD_REQ | MMC_I_TXFIFO_WR_REQ | + MMC_I_DAT_ERR); + + cmd->c_resid = cmd->c_datalen; + cmd->c_buf = cmd->c_data; + + cmdat |= CMDAT_DATA_EN; + } else { + cmd->c_resid = 0; + cmd->c_buf = NULL; + } + + /* + * "After reset, the MMC card must be initialized by sending + * 80 clocks to it on the MMCLK signal." [15.4.3.2] + */ + if (!ISSET(sc->sc_flags, PMF_CARD_INITED)) { + DPRINTF(1,("%s: first command\n", sc->sc_dev.dv_xname)); + cmdat |= CMDAT_INIT; + SET(sc->sc_flags, PMF_CARD_INITED); + } + + /* Begin the transfer and start the bus clock. */ + CSR_WRITE_4(sc, MMC_CMDAT, cmdat); + pxammc_clock_start(sc); + + /* Wait for it to complete (in no more than 2 seconds). */ + CSR_CLR_4(sc, MMC_I_MASK, MMC_I_END_CMD_RES | MMC_I_RES_ERR); + timo = 2; + sc->sc_cmd = cmd; + do { tsleep(sc, PWAIT, "mmcmd", hz); } + while (sc->sc_cmd == cmd && timo-- > 0); + + /* If it completed in time, SCF_ITSDONE is already set. */ + if (sc->sc_cmd == cmd) { + sc->sc_cmd = NULL; + cmd->c_error = ETIMEDOUT; + SET(cmd->c_flags, SCF_ITSDONE); + } + splx(s); +} + +void +pxammc_clock_stop(struct pxammc_softc *sc) +{ + if (ISSET(CSR_READ_4(sc, MMC_STAT), STAT_CLK_EN)) { + CSR_CLR_4(sc, MMC_I_MASK, MMC_I_CLK_IS_OFF); + CSR_WRITE_4(sc, MMC_STRPCL, STRPCL_STOP); + while (ISSET(CSR_READ_4(sc, MMC_STAT), STAT_CLK_EN)) + tsleep(sc, PWAIT, "mmclk", 0); + } +} + +void +pxammc_clock_start(struct pxammc_softc *sc) +{ + CSR_WRITE_4(sc, MMC_CLKRT, sc->sc_clkdiv); + CSR_WRITE_4(sc, MMC_STRPCL, STRPCL_START); +} + +int +pxammc_card_intr(void *arg) +{ + struct pxammc_softc *sc = arg; + + DPRINTF(1,("%s: card intr\n", sc->sc_dev.dv_xname)); + + /* Scan for inserted or removed cards. */ + sdmmc_needs_discover(sc->sc_sdmmc); + + return 1; +} + +int +pxammc_intr(void *arg) +{ + struct pxammc_softc *sc = arg; + int status; + +#define MMC_I_REG_STR "\20\001DATADONE\002PRGDONE\003ENDCMDRES" \ + "\004STOPCMD\005CLKISOFF\006RXFIFO\007TXFIFO" \ + "\011DATERR\012RESERR\014SDIO" + + status = CSR_READ_4(sc, MMC_I_REG) & ~CSR_READ_4(sc, MMC_I_MASK); + DPRINTF(1,("%s: intr %b\n", sc->sc_dev.dv_xname, status, + MMC_I_REG_STR)); + + /* + * Notify the process waiting in pxammc_clock_stop() when + * the clock has really stopped. + */ + if (ISSET(status, MMC_I_CLK_IS_OFF)) { + DPRINTF(2,("%s: clock is now off\n", sc->sc_dev.dv_xname)); + wakeup(sc); + CSR_SET_4(sc, MMC_I_MASK, MMC_I_CLK_IS_OFF); + CLR(status, MMC_I_CLK_IS_OFF); + } + + if (sc->sc_cmd == NULL) + goto end; + + if (ISSET(status, MMC_I_RES_ERR)) { + CSR_SET_4(sc, MMC_I_MASK, MMC_I_RES_ERR); + CLR(status, MMC_I_RES_ERR | MMC_I_END_CMD_RES); + sc->sc_cmd->c_error = ENOEXEC; + pxammc_intr_done(sc); + goto end; + } + + if (ISSET(status, MMC_I_END_CMD_RES)) { + pxammc_intr_cmd(sc); + CSR_SET_4(sc, MMC_I_MASK, MMC_I_END_CMD_RES); + CLR(status, MMC_I_END_CMD_RES); + if (sc->sc_cmd == NULL) + goto end; + } + + if (ISSET(status, MMC_I_TXFIFO_WR_REQ | MMC_I_RXFIFO_RD_REQ)) { + pxammc_intr_data(sc); + CLR(status, MMC_I_TXFIFO_WR_REQ | MMC_I_RXFIFO_RD_REQ); + } + + if (ISSET(status, MMC_I_DAT_ERR)) { + sc->sc_cmd->c_error = EIO; + pxammc_intr_done(sc); + CSR_SET_4(sc, MMC_I_MASK, MMC_I_DAT_ERR); + CLR(status, MMC_I_DAT_ERR); + goto end; + } + + if (ISSET(status, MMC_I_DATA_TRAN_DONE)) { + pxammc_intr_done(sc); + CSR_SET_4(sc, MMC_I_MASK, MMC_I_DATA_TRAN_DONE); + CLR(status, MMC_I_DATA_TRAN_DONE); + } + +end: + /* Avoid further unhandled interrupts. */ + if (status != 0) { +#ifdef DIAGNOSTIC + printf("%s: unhandled interrupt %b\n", sc->sc_dev.dv_xname, + status, MMC_I_REG_STR); +#endif + CSR_SET_4(sc, MMC_I_MASK, status); + } + + return 1; +} + +void +pxammc_intr_cmd(struct pxammc_softc *sc) +{ + struct sdmmc_command *cmd = sc->sc_cmd; + u_int32_t status; + int i; + +#define MMC_STAT_STR "\20\001READ_TIME_OUT\002TIMEOUT_RESPONSE" \ + "\003CRC_WRITE_ERROR\004CRC_READ_ERROR" \ + "\005SPI_READ_ERROR_TOKEN\006RES_CRC_ERR" \ + "\007XMIT_FIFO_EMPTY\010RECV_FIFO_FULL" \ + "\011CLK_EN\012FLASH_ERR\013SPI_WR_ERR" \ + "\014DATA_TRAN_DONE\015PRG_DONE\016END_CMD_RES" \ + "\017RD_STALLED\020SDIO_INT\021SDIO_SUSPEND_ACK" + +#define STAT_ERR (STAT_READ_TIME_OUT | STAT_TIMEOUT_RESPONSE | \ + STAT_CRC_WRITE_ERROR | STAT_CRC_READ_ERROR | \ + STAT_SPI_READ_ERROR_TOKEN | STAT_RES_CRC_ERR) + + if (ISSET(cmd->c_flags, SCF_RSP_136)) { + for (i = 3; i >= 0; i--) { + u_int32_t h = CSR_READ_4(sc, MMC_RES) & 0xffff; + u_int32_t l = CSR_READ_4(sc, MMC_RES) & 0xffff; + cmd->c_resp[i] = (h << 16) | l; + } + cmd->c_error = 0; + } else if (ISSET(cmd->c_flags, SCF_RSP_PRESENT)) { + /* + * Grrr... The processor manual is not clear about + * the layout of the response FIFO. It just states + * that the FIFO is 16 bits wide, has a depth of 8, + * and that the CRC is not copied into the FIFO. + * + * A 16-bit word in the FIFO is filled from highest + * to lowest bit as the response comes in. The two + * start bits and the 6 command index bits are thus + * stored in the upper 8 bits of the first 16-bit + * word that we read back from the FIFO. + * + * Since the sdmmc(4) framework expects the host + * controller to discard the first 8 bits of the + * response, what we must do is discard the upper + * byte of the first 16-bit word. + */ + u_int32_t h = CSR_READ_4(sc, MMC_RES) & 0xffff; + u_int32_t m = CSR_READ_4(sc, MMC_RES) & 0xffff; + u_int32_t l = CSR_READ_4(sc, MMC_RES) & 0xffff; + cmd->c_resp[0] = h << 24 | m << 8 | l >> 8; + for (i = 1; i < 4; i++) + cmd->c_resp[i] = 0; + cmd->c_error = 0; + } + + status = CSR_READ_4(sc, MMC_STAT); + + if (!ISSET(cmd->c_flags, SCF_RSP_PRESENT)) + status &= ~STAT_TIMEOUT_RESPONSE; + + /* XXX only for R6, not for R2 */ + if (!ISSET(cmd->c_flags, SCF_RSP_IDX)) + status &= ~STAT_RES_CRC_ERR; + + if (ISSET(status, STAT_TIMEOUT_RESPONSE)) + cmd->c_error = ETIMEDOUT; + else if (ISSET(status, STAT_ERR)) + cmd->c_error = EIO; + + if (cmd->c_error || cmd->c_datalen < 1) + pxammc_intr_done(sc); +} + +void +pxammc_intr_data(struct pxammc_softc *sc) +{ + struct sdmmc_command *cmd = sc->sc_cmd; + + DPRINTF(2,("%s: cmd %p resid %d\n", sc->sc_dev.dv_xname, + cmd, cmd->c_resid)); + + if (ISSET(cmd->c_flags, SCF_CMD_READ)) { + int n; + + n = MIN(32, cmd->c_resid); + cmd->c_resid -= n; + while (n-- > 0) + *cmd->c_buf++ = CSR_READ_1(sc, MMC_RXFIFO); + + if (cmd->c_resid > 0) + CSR_CLR_4(sc, MMC_I_MASK, MMC_I_RXFIFO_RD_REQ); + else + CSR_SET_4(sc, MMC_I_MASK, MMC_I_RXFIFO_RD_REQ); + } else { + int n; + int short_xfer = cmd->c_resid < 32; + + n = MIN(32, cmd->c_resid); + cmd->c_resid -= n; + for (n = MIN(32, cmd->c_resid); n > 0; n--) + CSR_WRITE_1(sc, MMC_TXFIFO, *cmd->c_buf++); + if (short_xfer) + CSR_WRITE_4(sc, MMC_PRTBUF, 1); + + if (cmd->c_resid > 0) + CSR_CLR_4(sc, MMC_I_MASK, MMC_I_TXFIFO_WR_REQ); + else + CSR_SET_4(sc, MMC_I_MASK, MMC_I_TXFIFO_WR_REQ); + } +} + +/* + * Wake up the process sleeping in pxammc_exec_command(). + */ +void +pxammc_intr_done(struct pxammc_softc *sc) +{ + u_int32_t status; + + status = CSR_READ_4(sc, MMC_STAT); + DPRINTF(1,("%s: status %b\n", sc->sc_dev.dv_xname, + status, MMC_STAT_STR)); + + CSR_SET_4(sc, MMC_I_MASK, MMC_I_TXFIFO_WR_REQ | + MMC_I_RXFIFO_RD_REQ | MMC_I_DATA_TRAN_DONE | + MMC_I_END_CMD_RES | MMC_I_RES_ERR | MMC_I_DAT_ERR); + + SET(sc->sc_cmd->c_flags, SCF_ITSDONE); + sc->sc_cmd = NULL; + wakeup(sc); +} diff --git a/sys/arch/arm/xscale/pxa2x0reg.h b/sys/arch/arm/xscale/pxa2x0reg.h index 19287398112..e53d35fa9c0 100644 --- a/sys/arch/arm/xscale/pxa2x0reg.h +++ b/sys/arch/arm/xscale/pxa2x0reg.h @@ -1,4 +1,4 @@ -/* $OpenBSD: pxa2x0reg.h,v 1.28 2006/10/19 10:55:56 tom Exp $ */ +/* $OpenBSD: pxa2x0reg.h,v 1.29 2007/03/18 20:53:10 uwe Exp $ */ /* $NetBSD: pxa2x0reg.h,v 1.4 2003/06/11 20:43:01 scw Exp $ */ /* @@ -114,8 +114,8 @@ #define PXA2X0_SSP1_BASE 0x41700000 /* PXA270 */ #define PXA2X0_SSP2_BASE 0x41900000 /* PXA270 */ #define PXA2X0_SSP_SIZE 0x40 -#define PXA2X0_MMC_BASE 0x41100000 /* MultiMediaCard */ -#define PXA2X0_MMC_SIZE 0x48 +#define PXA2X0_MMC_BASE 0x41100000 /* MultiMediaCard/SD/SDIO */ +#define PXA2X0_MMC_SIZE 0x50 #define PXA2X0_CLKMAN_BASE 0x41300000 /* Clock Manager */ #define PXA2X0_CLKMAN_SIZE 12 #define PXA2X0_LCDC_BASE 0x44000000 /* LCD Controller */ @@ -584,8 +584,8 @@ struct pxa2x0_dma_desc { #define STAT_CRC_READ_ERROR (1<<3) #define STAT_SPI_READ_ERROR_TOKEN (1<<4) #define STAT_RES_CRC_ERR (1<<5) -#define STAT_XMIT_FIFO_EMPTY (1<<6) -#define STAT_RECV_FIFO_FULL (1<<7) +#define STAT_XMIT_FIFO_EMPTY (1<<6) /* (PXA27x: reserved) */ +#define STAT_RECV_FIFO_FULL (1<<7) /* (PXA27x: reserved) */ #define STAT_CLK_EN (1<<8) #define STAT_DATA_TRAN_DONE (1<<11) #define STAT_PRG_DONE (1<<12) @@ -606,7 +606,7 @@ struct pxa2x0_dma_desc { #define MMC_CMDAT 0x10 /* command/response/data */ #define CMDAT_RESPONSE_FORMAT 0x03 #define CMDAT_RESPONSE_FORMAT_NO 0 /* no response */ -#define CMDAT_RESPONSE_FORMAT_R1 1 /* R1, R1b, R4, R5 */ +#define CMDAT_RESPONSE_FORMAT_R1 1 /* R1, R1b, R4, R5, R5b, R6 */ #define CMDAT_RESPONSE_FORMAT_R2 2 #define CMDAT_RESPONSE_FORMAT_R3 3 #define CMDAT_DATA_EN (1<<2) @@ -618,7 +618,7 @@ struct pxa2x0_dma_desc { #define MMC_RESTO 0x14 /* expected response time out */ #define MMC_RDTO 0x18 /* expected data read time out */ #define MMC_BLKLEN 0x1c /* block length of data transaction */ -#define MMC_NOB 0x20 /* number of blocks (block mode) */ +#define MMC_NUMBLK 0x20 /* number of blocks (block mode) */ #define MMC_PRTBUF 0x24 /* partial MMC_TXFIFO written */ #define PRTBUF_BUF_PART_FULL (1<<0) /* buffer partially full */ #define MMC_I_MASK 0x28 /* interrupt mask */ @@ -630,6 +630,9 @@ struct pxa2x0_dma_desc { #define MMC_I_CLK_IS_OFF (1<<4) #define MMC_I_RXFIFO_RD_REQ (1<<5) #define MMC_I_TXFIFO_WR_REQ (1<<6) +#define MMC_I_DAT_ERR (1<<8) /* PXA27x */ +#define MMC_I_RES_ERR (1<<9) /* PXA27x */ +#define MMC_I_SDIO_INT (1<<11) /* PXA27x */ #define MMC_CMD 0x30 /* index of current command */ #define MMC_ARGH 0x34 /* MSW part of the current command arg */ #define MMC_ARGL 0x38 /* LSW part of the current command arg */ diff --git a/sys/arch/arm/xscale/pxammcvar.h b/sys/arch/arm/xscale/pxammcvar.h new file mode 100644 index 00000000000..96031e381d4 --- /dev/null +++ b/sys/arch/arm/xscale/pxammcvar.h @@ -0,0 +1,42 @@ +/* $OpenBSD: pxammcvar.h,v 1.1 2007/03/18 20:53:10 uwe Exp $ */ + +/* + * Copyright (c) 2007 Uwe Stuehler <uwe@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <machine/bus.h> + +struct pxammc_tag { + void *cookie; + u_int32_t (*get_ocr)(void *); + int (*set_power)(void *, u_int32_t); +}; + +struct pxammc_softc { + struct device sc_dev; /* base device */ + struct pxammc_tag tag; /* attachment driver functions */ + bus_space_tag_t sc_iot; /* register space tag */ + bus_space_handle_t sc_ioh; /* register space handle */ + struct device *sc_sdmmc; /* generic sdmmc bus device */ + void *sc_card_ih; /* card interrupt handle */ + void *sc_ih; /* MMC interrupt handle */ + int sc_flags; /* driver state flags */ +#define PMF_CARD_INITED 0x0001 /* card init sequence sent */ + int sc_clkdiv; /* current clock divider */ + struct sdmmc_command * volatile sc_cmd; /* command in progress */ +}; + +int pxammc_match(void); +void pxammc_attach(struct pxammc_softc *, void *); diff --git a/sys/arch/zaurus/conf/GENERIC b/sys/arch/zaurus/conf/GENERIC index 8d601f24f7b..835e7c120a9 100644 --- a/sys/arch/zaurus/conf/GENERIC +++ b/sys/arch/zaurus/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.61 2007/02/28 21:54:43 grange Exp $ +# $OpenBSD: GENERIC,v 1.62 2007/03/18 20:53:10 uwe Exp $ # # For further information on compiling OpenBSD kernels, see the config(8) # man page. @@ -202,6 +202,10 @@ zrc0 at pxaip? # Zaurus remote control wskbd* at zrc? mux 1 flash0 at pxaip? # NAND flash memory +pxammc0 at pxaip? # MMC/SD/SDIO controller +sdmmc* at pxammc? # SD/MMC bus +scsibus* at sdmmc? # SCSI emulation + # 1-Wire devices option ONEWIREVERBOSE owid* at onewire? # ID diff --git a/sys/arch/zaurus/conf/files.zaurus b/sys/arch/zaurus/conf/files.zaurus index 29feac6abe4..29facd476b6 100644 --- a/sys/arch/zaurus/conf/files.zaurus +++ b/sys/arch/zaurus/conf/files.zaurus @@ -1,4 +1,4 @@ -# $OpenBSD: files.zaurus,v 1.23 2006/11/25 14:31:59 uwe Exp $ +# $OpenBSD: files.zaurus,v 1.24 2007/03/18 20:53:10 uwe Exp $ # # First try for arm-specific configuration info # @@ -42,6 +42,10 @@ file arch/zaurus/dev/zaurus_scoop.c scoop attach pxapcic at pxaip with pxapcic_scoop file arch/zaurus/dev/scoop_pcic.c pxapcic_scoop +# SD/MMC socket controller +attach pxammc at pxaip with pxammc_scoop +file arch/zaurus/dev/scoop_mmc.c pxammc_scoop + # Dedicated SSP unit for ADC, touch screen, and backlight device zssp attach zssp at pxaip @@ -102,6 +106,11 @@ include "dev/usb/files.usb" include "dev/mii/files.mii" # +# Machine-independent SD/MMC drivers +# +include "dev/sdmmc/files.sdmmc" + +# # Machine-independent 1-Wire drivers # include "dev/onewire/files.onewire" diff --git a/sys/arch/zaurus/dev/scoop_mmc.c b/sys/arch/zaurus/dev/scoop_mmc.c new file mode 100644 index 00000000000..18c4adfc987 --- /dev/null +++ b/sys/arch/zaurus/dev/scoop_mmc.c @@ -0,0 +1,78 @@ +/* $OpenBSD: scoop_mmc.c,v 1.1 2007/03/18 20:53:10 uwe Exp $ */ + +/* + * Copyright (c) 2007 Uwe Stuehler <uwe@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* Attachment driver for pxammc(4) on Zaurus */ + +#include <sys/param.h> +#include <sys/device.h> +#include <sys/systm.h> + +#include <dev/sdmmc/sdmmcreg.h> + +#include <arch/arm/xscale/pxammcvar.h> +#include <arch/zaurus/dev/zaurus_scoopvar.h> + +int scoop_mmc_match(struct device *, void *, void *); +void scoop_mmc_attach(struct device *, struct device *, void *); + +struct cfattach pxammc_scoop_ca = { + sizeof(struct pxammc_softc), scoop_mmc_match, + scoop_mmc_attach +}; + +u_int32_t scoop_mmc_get_ocr(void *); +int scoop_mmc_set_power(void *, u_int32_t); + +int +scoop_mmc_match(struct device *parent, void *match, void *aux) +{ + return pxammc_match(); +} + +void +scoop_mmc_attach(struct device *parent, struct device *self, void *aux) +{ + struct pxammc_softc *sc = (struct pxammc_softc *)self; + + sc->tag.cookie = (void *)sc; + sc->tag.get_ocr = scoop_mmc_get_ocr; + sc->tag.set_power = scoop_mmc_set_power; + + pxammc_attach(sc, aux); +} + +u_int32_t +scoop_mmc_get_ocr(void *cookie) +{ + return MMC_OCR_3_2V_3_3V | MMC_OCR_3_3V_3_4V; +} + +int +scoop_mmc_set_power(void *cookie, u_int32_t ocr) +{ + if (ISSET(ocr, MMC_OCR_3_2V_3_3V | MMC_OCR_3_3V_3_4V)) { + scoop_set_sdmmc_power(1); + return 0; + } else if (ocr != 0) { + printf("scoop_mmc_set_power: unsupported OCR (%#x)\n", ocr); + return EINVAL; + } else { + scoop_set_sdmmc_power(0); + return 0; + } +} diff --git a/sys/dev/sdmmc/sdmmcvar.h b/sys/dev/sdmmc/sdmmcvar.h index 2c2bdd27e14..13041d85368 100644 --- a/sys/dev/sdmmc/sdmmcvar.h +++ b/sys/dev/sdmmc/sdmmcvar.h @@ -1,4 +1,4 @@ -/* $OpenBSD: sdmmcvar.h,v 1.5 2006/11/29 14:16:43 uwe Exp $ */ +/* $OpenBSD: sdmmcvar.h,v 1.6 2007/03/18 20:53:10 uwe Exp $ */ /* * Copyright (c) 2006 Uwe Stuehler <uwe@openbsd.org> @@ -76,6 +76,7 @@ struct sdmmc_command { int c_blklen; /* block length */ int c_flags; /* see below */ #define SCF_ITSDONE 0x0001 /* command is complete */ +#define SCF_CMD(flags) ((flags) & 0x00f0) #define SCF_CMD_AC 0x0000 #define SCF_CMD_ADTC 0x0010 #define SCF_CMD_BC 0x0020 @@ -94,8 +95,12 @@ struct sdmmc_command { #define SCF_RSP_R4 (SCF_RSP_PRESENT) #define SCF_RSP_R5 (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX) #define SCF_RSP_R5B (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX|SCF_RSP_BSY) -#define SCF_RSP_R6 (SCF_RSP_PRESENT|SCF_RSP_CRC) +#define SCF_RSP_R6 (SCF_RSP_PRESENT|SCF_RSP_CRC|SCF_RSP_IDX) int c_error; /* errno value on completion */ + + /* Host controller owned fields for data xfer in progress */ + int c_resid; /* remaining I/O */ + u_char *c_buf; /* remaining data */ }; /* |