> Date: Thu, 28 Apr 2016 12:28:59 +0200 (CEST)
> From: Mark Kettenis <[email protected]>
>
> The diff below implements some speedups for sdhc(4). In particular:
>
> * Implement high speed mode
> * Implement support for 4-bit and 8-bit busses
> * Use DMA for block transfers
>
> High speed mode and wider bus support are only used for (e)MMC for
> now, but DMA support should benefit SD cards as well.
>
> Most of this code was derived from the imrpovements made over at
> NetBSD. However, I chose to only implement for ADMA2 and not for the
> older SDMA and ADMA1 mechanisms. These older DMA mechanism have
> severe limitations and it seems that most of the available hardware
> supports ADAM2. And of course PIO (programmed IO) will continue to
> work just fine for hardware that doesn't support ADMA2.
>
> Emulates SCSI transfers continue to be bounced through the sdmmc task
> thread. That probably limits the total throughput a bit.
> Nevertheless, with this diff I can read from eMMC in the Lenovo Bay
> Trail stick at 40MB/s, which is quite an improvement from the 5MB/s I
> reached before.
>
> This is probably several commit's worth of changes, but this provides
> them all in an easily testable form.
>
> Please test and/or review.
jsg@ noticed I missed ommc(4) on armv7. New diff below.
Index: dev/sdmmc/sdhc.c
===================================================================
RCS file: /cvs/src/sys/dev/sdmmc/sdhc.c,v
retrieving revision 1.43
diff -u -p -r1.43 sdhc.c
--- dev/sdmmc/sdhc.c 30 Mar 2016 09:58:01 -0000 1.43
+++ dev/sdmmc/sdhc.c 28 Apr 2016 11:24:08 -0000
@@ -36,6 +36,7 @@
#define SDHC_COMMAND_TIMEOUT hz
#define SDHC_BUFFER_TIMEOUT hz
#define SDHC_TRANSFER_TIMEOUT hz
+#define SDHC_DMA_TIMEOUT (hz*3)
struct sdhc_host {
struct sdhc_softc *sc; /* host controller device */
@@ -50,6 +51,10 @@ struct sdhc_host {
u_int8_t regs[14]; /* host controller state */
u_int16_t intr_status; /* soft interrupt status */
u_int16_t intr_error_status; /* soft error status */
+
+ bus_dmamap_t adma_map;
+ bus_dma_segment_t adma_segs[1];
+ caddr_t adma2;
};
/* flag values */
@@ -82,8 +87,10 @@ int sdhc_host_maxblklen(sdmmc_chipset_ha
int sdhc_card_detect(sdmmc_chipset_handle_t);
int sdhc_bus_power(sdmmc_chipset_handle_t, u_int32_t);
int sdhc_bus_clock(sdmmc_chipset_handle_t, int);
+int sdhc_bus_width(sdmmc_chipset_handle_t, int);
void sdhc_card_intr_mask(sdmmc_chipset_handle_t, int);
void sdhc_card_intr_ack(sdmmc_chipset_handle_t);
+int sdhc_signal_voltage(sdmmc_chipset_handle_t, int);
void sdhc_exec_command(sdmmc_chipset_handle_t, struct sdmmc_command *);
int sdhc_start_command(struct sdhc_host *, struct sdmmc_command *);
int sdhc_wait_state(struct sdhc_host *, u_int32_t, u_int32_t);
@@ -112,11 +119,13 @@ struct sdmmc_chip_functions sdhc_functio
/* bus power and clock frequency */
sdhc_bus_power,
sdhc_bus_clock,
+ sdhc_bus_width,
/* command execution */
sdhc_exec_command,
/* card interrupt */
sdhc_card_intr_mask,
- sdhc_card_intr_ack
+ sdhc_card_intr_ack,
+ sdhc_signal_voltage
};
struct cfdriver sdhc_cd = {
@@ -135,6 +144,7 @@ sdhc_host_found(struct sdhc_softc *sc, b
struct sdhc_host *hp;
int error = 1;
int max_clock;
+ uint32_t caps2 = 0;
#ifdef SDHC_DEBUG
u_int16_t version;
@@ -174,7 +184,7 @@ sdhc_host_found(struct sdhc_softc *sc, b
caps = HREAD4(hp, SDHC_CAPABILITIES);
/* Use DMA if the host system and the controller support it. */
- if (usedma && ISSET(caps, SDHC_DMA_SUPPORT))
+ if (usedma && ISSET(caps, SDHC_ADMA2_SUPP))
SET(hp->flags, SHF_USE_DMA);
/*
@@ -210,6 +220,14 @@ sdhc_host_found(struct sdhc_softc *sc, b
/*
* Determine SD bus voltage levels supported by the controller.
*/
+ if (ISSET(caps, SDHC_HIGH_SPEED_SUPP))
+ SET(hp->ocr, MMC_OCR_HCS);
+ if (ISSET(caps2, SDHC_SDR50_SUPP))
+ SET(hp->ocr, MMC_OCR_S18A);
+ if (ISSET(caps2, SDHC_DDR50_SUPP))
+ SET(hp->ocr, MMC_OCR_S18A);
+ if (ISSET(caps2, SDHC_SDR104_SUPP))
+ SET(hp->ocr, MMC_OCR_S18A);
if (ISSET(caps, SDHC_VOLTAGE_SUPP_1_8V))
SET(hp->ocr, MMC_OCR_1_65V_1_95V);
if (ISSET(caps, SDHC_VOLTAGE_SUPP_3_0V))
@@ -236,6 +254,46 @@ sdhc_host_found(struct sdhc_softc *sc, b
break;
}
+ if (ISSET(hp->flags, SHF_USE_DMA)) {
+ int rseg;
+
+ /* Allocate ADMA2 descriptor memory */
+ error = bus_dmamem_alloc(sc->sc_dmat, PAGE_SIZE, PAGE_SIZE,
+ PAGE_SIZE, hp->adma_segs, 1, &rseg,
+ BUS_DMA_WAITOK | BUS_DMA_ZERO);
+ if (error)
+ goto adma_done;
+ error = bus_dmamem_map(sc->sc_dmat, hp->adma_segs, rseg,
+ PAGE_SIZE, &hp->adma2, BUS_DMA_WAITOK);
+ if (error) {
+ bus_dmamem_free(sc->sc_dmat, hp->adma_segs, rseg);
+ goto adma_done;
+ }
+ error = bus_dmamap_create(sc->sc_dmat, PAGE_SIZE, 1, PAGE_SIZE,
+ 0, BUS_DMA_WAITOK, &hp->adma_map);
+ if (error) {
+ bus_dmamem_unmap(sc->sc_dmat, hp->adma2, PAGE_SIZE);
+ bus_dmamem_free(sc->sc_dmat, hp->adma_segs, rseg);
+ goto adma_done;
+ }
+ error = bus_dmamap_load(sc->sc_dmat, hp->adma_map,
+ hp->adma2, PAGE_SIZE, NULL,
+ BUS_DMA_WAITOK | BUS_DMA_WRITE);
+ if (error) {
+ bus_dmamap_destroy(sc->sc_dmat, hp->adma_map);
+ bus_dmamem_unmap(sc->sc_dmat, hp->adma2, PAGE_SIZE);
+ bus_dmamem_free(sc->sc_dmat, hp->adma_segs, rseg);
+ goto adma_done;
+ }
+
+ adma_done:
+ if (error) {
+ printf("%s: can't allocate DMA descriptor table\n",
+ DEVNAME(hp->sc));
+ CLR(hp->flags, SHF_USE_DMA);
+ }
+ }
+
/*
* Attach the generic SD/MMC bus driver. (The bus driver must
* not invoke any chipset functions before it is attached.)
@@ -244,6 +302,16 @@ sdhc_host_found(struct sdhc_softc *sc, b
saa.saa_busname = "sdmmc";
saa.sct = &sdhc_functions;
saa.sch = hp;
+ saa.dmat = sc->sc_dmat;
+ saa.caps = SMC_CAPS_4BIT_MODE;
+
+ if (ISSET(caps, SDHC_HIGH_SPEED_SUPP))
+ saa.caps |= SMC_CAPS_MMC_HIGHSPEED;
+ if (ISSET(caps, SDHC_8BIT_MODE_SUPP))
+ saa.caps |= SMC_CAPS_8BIT_MODE;
+
+ if (ISSET(hp->flags, SHF_USE_DMA))
+ saa.caps |= SMC_CAPS_DMA;
hp->sdmmc = config_found(&sc->sc_dev, &saa, NULL);
if (hp->sdmmc == NULL) {
@@ -486,6 +554,7 @@ sdhc_bus_clock(sdmmc_chipset_handle_t sc
int sdclk;
int timo;
int error = 0;
+// int ddr = 0;
s = splsdmmc();
@@ -503,6 +572,27 @@ sdhc_bus_clock(sdmmc_chipset_handle_t sc
if (freq == SDMMC_SDCLK_OFF)
goto ret;
+#if 0
+ if (SDHC_SPEC_VERSION(hp->version) >= SDHC_SPEC_V3) {
+ HCLR2(hp, SDHC_HOST_CTL2, SDHC_UHS_MODE_SELECT_MASK);
+ if (freq > 100000) {
+ HSET2(hp, SDHC_HOST_CTL2, SDHC_UHS_MODE_SELECT_SDR104);
+ } else if (freq > 50000) {
+ HSET2(hp, SDHC_HOST_CTL2, SDHC_UHS_MODE_SELECT_SDR50);
+ } else if (freq > 25000) {
+ if (ddr) {
+ HSET2(hp, SDHC_HOST_CTL2,
+ SDHC_UHS_MODE_SELECT_DDR50);
+ } else {
+ HSET2(hp, SDHC_HOST_CTL2,
+ SDHC_UHS_MODE_SELECT_SDR25);
+ }
+ } else if (freq > 400) {
+ HSET2(hp, SDHC_HOST_CTL2, SDHC_UHS_MODE_SELECT_SDR12);
+ }
+ }
+#endif
+
/*
* Set the minimum base clock frequency divisor.
*/
@@ -536,11 +626,53 @@ sdhc_bus_clock(sdmmc_chipset_handle_t sc
*/
HSET2(hp, SDHC_CLOCK_CTL, SDHC_SDCLK_ENABLE);
+ if (freq > 25000)
+ HSET1(hp, SDHC_HOST_CTL, SDHC_HIGH_SPEED);
+ else
+ HCLR1(hp, SDHC_HOST_CTL, SDHC_HIGH_SPEED);
+
ret:
splx(s);
return error;
}
+int
+sdhc_bus_width(sdmmc_chipset_handle_t sch, int width)
+{
+ struct sdhc_host *hp = (struct sdhc_host *)sch;
+ int reg;
+ int s;
+
+ switch (width) {
+ case 1:
+ case 4:
+ case 8:
+ break;
+ default:
+ DPRINTF(0,("%s: unsupported bus width (%d)\n",
+ DEVNAME(hp->sc), width));
+ return 1;
+ }
+
+ s = splsdmmc();
+
+ reg = HREAD1(hp, SDHC_HOST_CTL);
+ reg &= ~SDHC_4BIT_MODE;
+ if (SDHC_SPEC_VERSION(hp->version) >= SDHC_SPEC_V3) {
+ reg &= ~SDHC_8BIT_MODE;
+ }
+ if (width == 4) {
+ reg |= SDHC_4BIT_MODE;
+ } else if (width == 8 && SDHC_SPEC_VERSION(hp->version) >=
SDHC_SPEC_V3) {
+ reg |= SDHC_8BIT_MODE;
+ }
+ HWRITE1(hp, SDHC_HOST_CTL, reg);
+
+ splx(s);
+
+ return 0;
+}
+
void
sdhc_card_intr_mask(sdmmc_chipset_handle_t sch, int enable)
{
@@ -564,6 +696,32 @@ sdhc_card_intr_ack(sdmmc_chipset_handle_
}
int
+sdhc_signal_voltage(sdmmc_chipset_handle_t sch, int signal_voltage)
+{
+ struct sdhc_host *hp = (struct sdhc_host *)sch;
+ int error = 0;
+ int s;
+
+ s = splsdmmc();
+
+ switch (signal_voltage) {
+ case SDMMC_SIGNAL_VOLTAGE_180:
+ HSET2(hp, SDHC_HOST_CTL2, SDHC_1_8V_SIGNAL_EN);
+ break;
+ case SDMMC_SIGNAL_VOLTAGE_330:
+ HCLR2(hp, SDHC_HOST_CTL2, SDHC_1_8V_SIGNAL_EN);
+ break;
+ default:
+ error = EINVAL;
+ break;
+ }
+
+ splx(s);
+
+ return error;
+}
+
+int
sdhc_wait_state(struct sdhc_host *hp, u_int32_t mask, u_int32_t value)
{
u_int32_t state;
@@ -641,11 +799,14 @@ sdhc_exec_command(sdmmc_chipset_handle_t
int
sdhc_start_command(struct sdhc_host *hp, struct sdmmc_command *cmd)
{
+ struct sdhc_adma2_descriptor32 *desc = (void *)hp->adma2;
+ struct sdhc_softc *sc = hp->sc;
u_int16_t blksize = 0;
u_int16_t blkcount = 0;
u_int16_t mode;
u_int16_t command;
int error;
+ int seg;
int s;
DPRINTF(1,("%s: start cmd %u arg=%#x data=%#x dlen=%d flags=%#x "
@@ -688,10 +849,9 @@ sdhc_start_command(struct sdhc_host *hp,
mode |= SDHC_AUTO_CMD12_ENABLE;
}
}
-#ifdef notyet
- if (ISSET(hp->flags, SHF_USE_DMA))
+ if (cmd->c_dmamap && cmd->c_datalen > 0 &&
+ ISSET(hp->flags, SHF_USE_DMA))
mode |= SDHC_DMA_ENABLE;
-#endif
/*
* Prepare command register value. (2.2.6)
@@ -725,6 +885,34 @@ sdhc_start_command(struct sdhc_host *hp,
HSET1(hp, SDHC_HOST_CTL, SDHC_LED_ON);
/* XXX: Set DMA start address if SHF_USE_DMA is set. */
+ if (cmd->c_dmamap && ISSET(hp->flags, SHF_USE_DMA)) {
+ for (seg = 0; seg < cmd->c_dmamap->dm_nsegs; seg++) {
+ bus_addr_t paddr =
+ cmd->c_dmamap->dm_segs[seg].ds_addr;
+ uint16_t len =
+ cmd->c_dmamap->dm_segs[seg].ds_len == 65536 ?
+ 0 : cmd->c_dmamap->dm_segs[seg].ds_len;
+ uint16_t attr;
+
+ attr = SDHC_ADMA2_VALID | SDHC_ADMA2_ACT_TRANS;
+ if (seg == cmd->c_dmamap->dm_nsegs - 1)
+ attr |= SDHC_ADMA2_END;
+
+ desc[seg].attribute = htole16(attr);
+ desc[seg].length = htole16(len);
+ desc[seg].address = htole32(paddr);
+ }
+
+ desc[cmd->c_dmamap->dm_nsegs].attribute = htole16(0);
+
+ bus_dmamap_sync(sc->sc_dmat, hp->adma_map, 0, PAGE_SIZE,
+ BUS_DMASYNC_PREWRITE);
+
+ HCLR1(hp, SDHC_HOST_CTL, SDHC_DMA_SELECT);
+ HSET1(hp, SDHC_HOST_CTL, SDHC_DMA_SELECT_ADMA2);
+
+ HWRITE4(hp, SDHC_ADMA_SYSTEM_ADDR,
hp->adma_map->dm_segs[0].ds_addr);
+ }
DPRINTF(1,("%s: cmd=%#x mode=%#x blksize=%d blkcount=%d\n",
DEVNAME(hp->sc), command, mode, blksize, blkcount));
@@ -752,6 +940,25 @@ sdhc_transfer_data(struct sdhc_host *hp,
int mask;
int error;
+ if (cmd->c_dmamap) {
+ int status;
+
+ error = 0;
+ for (;;) {
+ status = sdhc_wait_intr(hp,
+ SDHC_DMA_INTERRUPT|SDHC_TRANSFER_COMPLETE,
+ SDHC_DMA_TIMEOUT);
+ if (status & SDHC_TRANSFER_COMPLETE)
+ break;
+ if (!status) {
+ error = ETIMEDOUT;
+ break;
+ }
+ }
+
+ goto done;
+ }
+
mask = ISSET(cmd->c_flags, SCF_CMD_READ) ?
SDHC_BUFFER_READ_ENABLE : SDHC_BUFFER_WRITE_ENABLE;
error = 0;
@@ -792,6 +999,7 @@ sdhc_transfer_data(struct sdhc_host *hp,
SDHC_TRANSFER_TIMEOUT))
error = ETIMEDOUT;
+done:
if (error != 0)
cmd->c_error = error;
SET(cmd->c_flags, SCF_ITSDONE);
Index: dev/sdmmc/sdhcreg.h
===================================================================
RCS file: /cvs/src/sys/dev/sdmmc/sdhcreg.h,v
retrieving revision 1.5
diff -u -p -r1.5 sdhcreg.h
--- dev/sdmmc/sdhcreg.h 11 Jan 2016 06:54:53 -0000 1.5
+++ dev/sdmmc/sdhcreg.h 28 Apr 2016 11:24:08 -0000
@@ -80,6 +80,10 @@
#define SDHC_CMD_INHIBIT_CMD (1<<0)
#define SDHC_CMD_INHIBIT_MASK 0x0003
#define SDHC_HOST_CTL 0x28
+#define SDHC_8BIT_MODE (1<<5)
+#define SDHC_DMA_SELECT (3<<3)
+#define SDHC_DMA_SELECT_SDMA (0<<3)
+#define SDHC_DMA_SELECT_ADMA2 (2<<3)
#define SDHC_HIGH_SPEED (1<<2)
#define SDHC_4BIT_MODE (1<<1)
#define SDHC_LED_ON (1<<0)
@@ -137,12 +141,25 @@
#define SDHC_EINTR_SIGNAL_EN 0x3a
#define SDHC_EINTR_SIGNAL_MASK 0x01ff /* excluding vendor
signals */
#define SDHC_CMD12_ERROR_STATUS 0x3c
+#define SDHC_HOST_CTL2 0x3e
+#define SDHC_SAMPLING_CLOCK_SEL (1<<7)
+#define SDHC_EXECUTE_TUNING (1<<6)
+#define SDHC_1_8V_SIGNAL_EN (1<<3)
+#define SDHC_UHS_MODE_SELECT_SHIFT 0
+#define SDHC_UHS_MODE_SELECT_MASK 0x7
+#define SDHC_UHS_MODE_SELECT_SDR12 0
+#define SDHC_UHS_MODE_SELECT_SDR25 1
+#define SDHC_UHS_MODE_SELECT_SDR50 2
+#define SDHC_UHS_MODE_SELECT_SDR104 3
+#define SDHC_UHS_MODE_SELECT_DDR50 4
#define SDHC_CAPABILITIES 0x40
#define SDHC_VOLTAGE_SUPP_1_8V (1<<26)
#define SDHC_VOLTAGE_SUPP_3_0V (1<<25)
#define SDHC_VOLTAGE_SUPP_3_3V (1<<24)
-#define SDHC_DMA_SUPPORT (1<<22)
+#define SDHC_SDMA_SUPP (1<<22)
#define SDHC_HIGH_SPEED_SUPP (1<<21)
+#define SDHC_ADMA2_SUPP (1<<19)
+#define SDHC_8BIT_MODE_SUPP (1<<18)
#define SDHC_MAX_BLK_LEN_512 0
#define SDHC_MAX_BLK_LEN_1024 1
#define SDHC_MAX_BLK_LEN_2048 2
@@ -154,7 +171,27 @@
#define SDHC_TIMEOUT_FREQ_UNIT (1<<7) /* 0=KHz, 1=MHz */
#define SDHC_TIMEOUT_FREQ_SHIFT 0
#define SDHC_TIMEOUT_FREQ_MASK 0x1f
-#define SDHC_CAPABILITIES_1 0x44
+#define SDHC_CAPABILITIES2 0x44
+#define SDHC_SDR50_SUPP (1<<0)
+#define SDHC_SDR104_SUPP (1<<1)
+#define SDHC_DDR50_SUPP (1<<2)
+#define SDHC_DRIVER_TYPE_A (1<<4)
+#define SDHC_DRIVER_TYPE_C (1<<5)
+#define SDHC_DRIVER_TYPE_D (1<<6)
+#define SDHC_TIMER_COUNT_SHIFT 8
+#define SDHC_TIMER_COUNT_MASK 0xf
+#define SDHC_TUNING_SDR50 (1<<13)
+#define SDHC_RETUNING_MODES_SHIFT 14
+#define SDHC_RETUNING_MODES_MASK 0x3
+#define SDHC_RETUNING_MODE_1 (0 << SDHC_RETUNING_MODES_SHIFT)
+#define SDHC_RETUNING_MODE_2 (1 << SDHC_RETUNING_MODES_SHIFT)
+#define SDHC_RETUNING_MODE_3 (2 << SDHC_RETUNING_MODES_SHIFT)
+#define SDHC_CLOCK_MULTIPLIER_SHIFT 16
+#define SDHC_CLOCK_MULTIPLIER_MASK 0xff
+#define SDHC_ADMA_ERROR_STATUS 0x54
+#define SDHC_ADMA_LENGTH_MISMATCH (1<<2)
+#define SDHC_ADMA_ERROR_STATE (3<<0)
+#define SDHC_ADMA_SYSTEM_ADDR 0x58
#define SDHC_MAX_CAPABILITIES 0x48
#define SDHC_SLOT_INTR_STATUS 0xfc
#define SDHC_HOST_CTL_VERSION 0xfe
@@ -201,5 +238,19 @@
"\20\11ACMD12\10CL\7DEB\6DCRC\5DT\4CI\3CEB\2CCRC\1CT"
#define SDHC_CAPABILITIES_BITS \
"\20\33Vdd1.8V\32Vdd3.0V\31Vdd3.3V\30SUSPEND\27DMA\26HIGHSPEED"
+
+#define SDHC_ADMA2_VALID (1<<0)
+#define SDHC_ADMA2_END (1<<1)
+#define SDHC_ADMA2_INT (1<<2)
+#define SDHC_ADMA2_ACT (3<<4)
+#define SDHC_ADMA2_ACT_NOP (0<<4)
+#define SDHC_ADMA2_ACT_TRANS (2<<4)
+#define SDHC_ADMA2_ACT_LINK (3<<4)
+
+struct sdhc_adma2_descriptor32 {
+ uint16_t attribute;
+ uint16_t length;
+ uint32_t address;
+} __packed;
#endif
Index: dev/sdmmc/sdhcvar.h
===================================================================
RCS file: /cvs/src/sys/dev/sdmmc/sdhcvar.h,v
retrieving revision 1.8
diff -u -p -r1.8 sdhcvar.h
--- dev/sdmmc/sdhcvar.h 30 Mar 2016 09:58:01 -0000 1.8
+++ dev/sdmmc/sdhcvar.h 28 Apr 2016 11:24:08 -0000
@@ -29,6 +29,8 @@ struct sdhc_softc {
int sc_nhosts;
u_int sc_flags;
+ bus_dma_tag_t sc_dmat;
+
int (*sc_card_detect)(struct sdhc_softc *);
};
Index: dev/sdmmc/sdmmc.c
===================================================================
RCS file: /cvs/src/sys/dev/sdmmc/sdmmc.c,v
retrieving revision 1.39
diff -u -p -r1.39 sdmmc.c
--- dev/sdmmc/sdmmc.c 19 Mar 2016 11:41:56 -0000 1.39
+++ dev/sdmmc/sdmmc.c 28 Apr 2016 11:24:08 -0000
@@ -97,14 +97,25 @@ sdmmc_attach(struct device *parent, stru
{
struct sdmmc_softc *sc = (struct sdmmc_softc *)self;
struct sdmmcbus_attach_args *saa = aux;
+ int error;
printf("\n");
sc->sct = saa->sct;
sc->sch = saa->sch;
+ sc->sc_dmat = saa->dmat;
sc->sc_flags = saa->flags;
sc->sc_caps = saa->caps;
sc->sc_max_xfer = saa->max_xfer;
+
+ if (ISSET(sc->sc_caps, SMC_CAPS_DMA)) {
+ error = bus_dmamap_create(sc->sc_dmat, MAXPHYS, SDMMC_MAXNSEGS,
+ MAXPHYS, 0, BUS_DMA_NOWAIT|BUS_DMA_ALLOCNOW, &sc->sc_dmap);
+ if (error) {
+ printf("QQQ\n");
+ return;
+ }
+ }
SIMPLEQ_INIT(&sc->sf_head);
TAILQ_INIT(&sc->sc_tskq);
Index: dev/sdmmc/sdmmc_mem.c
===================================================================
RCS file: /cvs/src/sys/dev/sdmmc/sdmmc_mem.c,v
retrieving revision 1.22
diff -u -p -r1.22 sdmmc_mem.c
--- dev/sdmmc/sdmmc_mem.c 8 Nov 2015 12:10:27 -0000 1.22
+++ dev/sdmmc/sdmmc_mem.c 28 Apr 2016 11:24:08 -0000
@@ -40,16 +40,18 @@ int sdmmc_mem_set_blocklen(struct sdmmc_
int sdmmc_mem_send_cxd_data(struct sdmmc_softc *, int, void *, size_t);
int sdmmc_mem_mmc_switch(struct sdmmc_function *, uint8_t, uint8_t,
uint8_t);
+int sdmmc_mem_signal_voltage(struct sdmmc_softc *, int);
+
int sdmmc_mem_sd_init(struct sdmmc_softc *, struct sdmmc_function *);
int sdmmc_mem_mmc_init(struct sdmmc_softc *, struct sdmmc_function *);
int sdmmc_mem_single_read_block(struct sdmmc_function *, int, u_char *,
size_t);
-int sdmmc_mem_read_block_subr(struct sdmmc_function *, int, u_char *,
- size_t);
+int sdmmc_mem_read_block_subr(struct sdmmc_function *, bus_dmamap_t,
+ int, u_char *, size_t);
int sdmmc_mem_single_write_block(struct sdmmc_function *, int, u_char *,
size_t);
-int sdmmc_mem_write_block_subr(struct sdmmc_function *, int, u_char *,
- size_t);
+int sdmmc_mem_write_block_subr(struct sdmmc_function *, bus_dmamap_t,
+ int, u_char *, size_t);
#ifdef SDMMC_DEBUG
#define DPRINTF(s) printf s
@@ -65,6 +67,8 @@ sdmmc_mem_enable(struct sdmmc_softc *sc)
{
u_int32_t host_ocr;
u_int32_t card_ocr;
+ u_int32_t new_ocr;
+ int error;
rw_assert_wrlock(&sc->sc_lock);
@@ -116,13 +120,79 @@ sdmmc_mem_enable(struct sdmmc_softc *sc)
host_ocr |= SD_OCR_SDHC_CAP;
/* Send the new OCR value until all cards are ready. */
- if (sdmmc_mem_send_op_cond(sc, host_ocr, NULL) != 0) {
+ if (sdmmc_mem_send_op_cond(sc, host_ocr, &new_ocr) != 0) {
DPRINTF(("%s: can't send memory OCR\n", DEVNAME(sc)));
return 1;
}
+
+ if (ISSET(sc->sc_flags, SMF_SD_MODE) && ISSET(new_ocr, MMC_OCR_S18A)) {
+ /*
+ * Card and host support low voltage mode, begin switch
+ * sequence.
+ */
+ struct sdmmc_command cmd;
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.c_arg = 0;
+ cmd.c_flags = SCF_CMD_AC | SCF_RSP_R1;
+ cmd.c_opcode = SD_VOLTAGE_SWITCH;
+ DPRINTF(("%s: switching card to 1.8V\n", SDMMCDEVNAME(sc)));
+ error = sdmmc_mmc_command(sc, &cmd);
+ if (error) {
+ DPRINTF(("%s: voltage switch command failed\n",
+ DEVNAME(sc)));
+ return 1;
+ }
+
+ error = sdmmc_mem_signal_voltage(sc, SDMMC_SIGNAL_VOLTAGE_180);
+ if (error)
+ return 1;
+
+ SET(sc->sc_flags, SMF_UHS_MODE);
+ }
+
return 0;
}
+int
+sdmmc_mem_signal_voltage(struct sdmmc_softc *sc, int signal_voltage)
+{
+ int error;
+
+ /*
+ * Stop the clock
+ */
+ error = sdmmc_chip_bus_clock(sc->sct, sc->sch, SDMMC_SDCLK_OFF);
+ if (error)
+ goto out;
+
+ delay(1000);
+
+ /*
+ * Card switch command was successful, update host controller
+ * signal voltage setting.
+ */
+ DPRINTF(("%s: switching host to %s\n", DEVNAME(sc),
+ signal_voltage == SDMMC_SIGNAL_VOLTAGE_180 ? "1.8V" : "3.3V"));
+ error = sdmmc_chip_signal_voltage(sc->sct,
+ sc->sch, signal_voltage);
+ if (error)
+ goto out;
+
+ delay(5000);
+
+ /*
+ * Switch to SDR12 timing
+ */
+ error = sdmmc_chip_bus_clock(sc->sct, sc->sch, 25000);
+ if (error)
+ goto out;
+
+ delay(1000);
+
+out:
+ return error;
+}
+
/*
* Read the CSD and CID from all cards and assign each card a unique
* relative card address (RCA). CMD2 is ignored by SDIO-only cards.
@@ -424,6 +494,7 @@ sdmmc_mem_sd_init(struct sdmmc_softc *sc
int
sdmmc_mem_mmc_init(struct sdmmc_softc *sc, struct sdmmc_function *sf)
{
+ int width, value;
int error = 0;
u_int8_t ext_csd[512];
int speed = 0;
@@ -449,6 +520,34 @@ sdmmc_mem_mmc_init(struct sdmmc_softc *s
printf("%s: unknown CARD_TYPE 0x%x\n", DEVNAME(sc),
ext_csd[EXT_CSD_CARD_TYPE]);
}
+
+ if (ISSET(sc->sc_caps, SMC_CAPS_8BIT_MODE)) {
+ width = 8;
+ value = EXT_CSD_BUS_WIDTH_8;
+ } else if (ISSET(sc->sc_caps, SMC_CAPS_4BIT_MODE)) {
+ width = 4;
+ value = EXT_CSD_BUS_WIDTH_4;
+ } else {
+ width = 1;
+ value = EXT_CSD_BUS_WIDTH_1;
+ }
+
+ if (width != 1) {
+ error = sdmmc_mem_mmc_switch(sf, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_BUS_WIDTH, value);
+ if (error == 0)
+ error = sdmmc_chip_bus_width(sc->sct,
+ sc->sch, width);
+ else {
+ DPRINTF(("%s: can't change bus width"
+ " (%d bit)\n", SDMMCDEVNAME(sc), width));
+ return error;
+ }
+
+ /* XXXX: need bus test? (using by CMD14 & CMD19) */
+ delay(10000);
+ }
+
if (!ISSET(sc->sc_caps, SMC_CAPS_MMC_HIGHSPEED))
hs_timing = 0;
@@ -461,10 +560,11 @@ sdmmc_mem_mmc_init(struct sdmmc_softc *s
DEVNAME(sc));
return error;
}
+
+ delay(10000);
}
- error =
- sdmmc_chip_bus_clock(sc->sct, sc->sch, speed);
+ error = sdmmc_chip_bus_clock(sc->sct, sc->sch, speed);
if (error != 0) {
printf("%s: can't change bus clock\n", DEVNAME(sc));
return error;
@@ -478,7 +578,7 @@ sdmmc_mem_mmc_init(struct sdmmc_softc *s
printf("%s: can't re-read EXT_CSD\n",
DEVNAME(sc));
return error;
}
- if (ext_csd[EXT_CSD_HS_TIMING] != 1) {
+ if (ext_csd[EXT_CSD_HS_TIMING] != hs_timing) {
printf("%s, HS_TIMING set failed\n",
DEVNAME(sc));
return EINVAL;
}
@@ -566,8 +666,8 @@ sdmmc_mem_set_blocklen(struct sdmmc_soft
}
int
-sdmmc_mem_read_block_subr(struct sdmmc_function *sf, int blkno, u_char *data,
- size_t datalen)
+sdmmc_mem_read_block_subr(struct sdmmc_function *sf, bus_dmamap_t dmap,
+ int blkno, u_char *data, size_t datalen)
{
struct sdmmc_softc *sc = sf->sc;
struct sdmmc_command cmd;
@@ -588,6 +688,7 @@ sdmmc_mem_read_block_subr(struct sdmmc_f
else
cmd.c_arg = blkno << 9;
cmd.c_flags = SCF_CMD_ADTC | SCF_CMD_READ | SCF_RSP_R1;
+ cmd.c_dmamap = dmap;
error = sdmmc_mmc_command(sc, &cmd);
if (error != 0)
@@ -627,8 +728,8 @@ sdmmc_mem_single_read_block(struct sdmmc
int i;
for (i = 0; i < datalen / sf->csd.sector_size; i++) {
- error = sdmmc_mem_read_block_subr(sf, blkno + i, data + i *
- sf->csd.sector_size, sf->csd.sector_size);
+ error = sdmmc_mem_read_block_subr(sf,NULL, blkno + i,
+ data + i * sf->csd.sector_size, sf->csd.sector_size);
if (error)
break;
}
@@ -647,17 +748,42 @@ sdmmc_mem_read_block(struct sdmmc_functi
if (ISSET(sc->sc_caps, SMC_CAPS_SINGLE_ONLY)) {
error = sdmmc_mem_single_read_block(sf, blkno, data, datalen);
- } else {
- error = sdmmc_mem_read_block_subr(sf, blkno, data, datalen);
+ goto out;
}
+ if (!ISSET(sc->sc_caps, SMC_CAPS_DMA)) {
+ error = sdmmc_mem_read_block_subr(sf, NULL, blkno,
+ data, datalen);
+ goto out;
+ }
+
+ /* DMA transfer */
+ error = bus_dmamap_load(sc->sc_dmat, sc->sc_dmap, data, datalen,
+ NULL, BUS_DMA_NOWAIT|BUS_DMA_READ);
+ if (error)
+ goto out;
+
+ bus_dmamap_sync(sc->sc_dmat, sc->sc_dmap, 0, datalen,
+ BUS_DMASYNC_PREREAD);
+
+ error = sdmmc_mem_read_block_subr(sf, sc->sc_dmap, blkno, data,
+ datalen);
+ if (error)
+ goto unload;
+
+ bus_dmamap_sync(sc->sc_dmat, sc->sc_dmap, 0, datalen,
+ BUS_DMASYNC_POSTREAD);
+unload:
+ bus_dmamap_unload(sc->sc_dmat, sc->sc_dmap);
+
+out:
rw_exit(&sc->sc_lock);
return (error);
}
int
-sdmmc_mem_write_block_subr(struct sdmmc_function *sf, int blkno, u_char *data,
- size_t datalen)
+sdmmc_mem_write_block_subr(struct sdmmc_function *sf, bus_dmamap_t dmap,
+ int blkno, u_char *data, size_t datalen)
{
struct sdmmc_softc *sc = sf->sc;
struct sdmmc_command cmd;
@@ -677,6 +803,7 @@ sdmmc_mem_write_block_subr(struct sdmmc_
else
cmd.c_arg = blkno << 9;
cmd.c_flags = SCF_CMD_ADTC | SCF_RSP_R1;
+ cmd.c_dmamap = dmap;
error = sdmmc_mmc_command(sc, &cmd);
if (error != 0)
@@ -715,8 +842,8 @@ sdmmc_mem_single_write_block(struct sdmm
int i;
for (i = 0; i < datalen / sf->csd.sector_size; i++) {
- error = sdmmc_mem_write_block_subr(sf, blkno + i, data + i *
- sf->csd.sector_size, sf->csd.sector_size);
+ error = sdmmc_mem_write_block_subr(sf, NULL, blkno + i,
+ data + i * sf->csd.sector_size, sf->csd.sector_size);
if (error)
break;
}
@@ -735,10 +862,35 @@ sdmmc_mem_write_block(struct sdmmc_funct
if (ISSET(sc->sc_caps, SMC_CAPS_SINGLE_ONLY)) {
error = sdmmc_mem_single_write_block(sf, blkno, data, datalen);
- } else {
- error = sdmmc_mem_write_block_subr(sf, blkno, data, datalen);
+ goto out;
}
+ if (!ISSET(sc->sc_caps, SMC_CAPS_DMA)) {
+ error = sdmmc_mem_write_block_subr(sf, NULL, blkno,
+ data, datalen);
+ goto out;
+ }
+
+ /* DMA transfer */
+ error = bus_dmamap_load(sc->sc_dmat, sc->sc_dmap, data, datalen,
+ NULL, BUS_DMA_NOWAIT|BUS_DMA_WRITE);
+ if (error)
+ goto out;
+
+ bus_dmamap_sync(sc->sc_dmat, sc->sc_dmap, 0, datalen,
+ BUS_DMASYNC_PREWRITE);
+
+ error = sdmmc_mem_write_block_subr(sf, sc->sc_dmap, blkno, data,
+ datalen);
+ if (error)
+ goto unload;
+
+ bus_dmamap_sync(sc->sc_dmat, sc->sc_dmap, 0, datalen,
+ BUS_DMASYNC_POSTWRITE);
+unload:
+ bus_dmamap_unload(sc->sc_dmat, sc->sc_dmap);
+
+out:
rw_exit(&sc->sc_lock);
return (error);
}
Index: dev/sdmmc/sdmmcchip.h
===================================================================
RCS file: /cvs/src/sys/dev/sdmmc/sdmmcchip.h,v
retrieving revision 1.5
diff -u -p -r1.5 sdmmcchip.h
--- dev/sdmmc/sdmmcchip.h 12 Sep 2013 11:54:04 -0000 1.5
+++ dev/sdmmc/sdmmcchip.h 28 Apr 2016 11:24:08 -0000
@@ -19,6 +19,8 @@
#ifndef _SDMMC_CHIP_H_
#define _SDMMC_CHIP_H_
+#include <machine/bus.h>
+
struct sdmmc_command;
typedef struct sdmmc_chip_functions *sdmmc_chipset_tag_t;
@@ -32,15 +34,21 @@ struct sdmmc_chip_functions {
int (*host_maxblklen)(sdmmc_chipset_handle_t);
/* card detection */
int (*card_detect)(sdmmc_chipset_handle_t);
- /* bus power and clock frequency */
+ /* bus power, clock frequency and width */
int (*bus_power)(sdmmc_chipset_handle_t, u_int32_t);
int (*bus_clock)(sdmmc_chipset_handle_t, int);
+ int (*bus_width)(sdmmc_chipset_handle_t, int);
/* command execution */
void (*exec_command)(sdmmc_chipset_handle_t,
struct sdmmc_command *);
/* card interrupt */
void (*card_intr_mask)(sdmmc_chipset_handle_t, int);
void (*card_intr_ack)(sdmmc_chipset_handle_t);
+
+ /* UHS functions */
+ int (*signal_voltage)(sdmmc_chipset_handle_t, int);
+ int (*bus_clock_ddr)(sdmmc_chipset_handle_t, int, bool);
+ int (*execute_tuning)(sdmmc_chipset_handle_t, int);
};
/* host controller reset */
@@ -59,6 +67,8 @@ struct sdmmc_chip_functions {
((tag)->bus_power((handle), (ocr)))
#define sdmmc_chip_bus_clock(tag, handle, freq)
\
((tag)->bus_clock((handle), (freq)))
+#define sdmmc_chip_bus_width(tag, handle, width) \
+ ((tag)->bus_width((handle), (width)))
/* command execution */
#define sdmmc_chip_exec_command(tag, handle, cmdp) \
((tag)->exec_command((handle), (cmdp)))
@@ -67,16 +77,26 @@ struct sdmmc_chip_functions {
((tag)->card_intr_mask((handle), (enable)))
#define sdmmc_chip_card_intr_ack(tag, handle) \
((tag)->card_intr_ack((handle)))
+/* UHS functions */
+#define sdmmc_chip_signal_voltage(tag, handle, voltage)
\
+ ((tag)->signal_voltage((handle), (voltage)))
+#define sdmmc_chip_execute_tuning(tag, handle, timing) \
+ ((tag)->execute_tuning ? (tag)->execute_tuning((handle), (timing)) :
EINVAL)
/* clock frequencies for sdmmc_chip_bus_clock() */
#define SDMMC_SDCLK_OFF 0
#define SDMMC_SDCLK_400KHZ 400
#define SDMMC_SDCLK_25MHZ 25000
+/* voltage levels for sdmmc_chip_signal_voltage() */
+#define SDMMC_SIGNAL_VOLTAGE_330 0
+#define SDMMC_SIGNAL_VOLTAGE_180 1
+
struct sdmmcbus_attach_args {
const char *saa_busname;
sdmmc_chipset_tag_t sct;
sdmmc_chipset_handle_t sch;
+ bus_dma_tag_t dmat;
int flags;
int caps;
long max_xfer;
Index: dev/sdmmc/sdmmcreg.h
===================================================================
RCS file: /cvs/src/sys/dev/sdmmc/sdmmcreg.h,v
retrieving revision 1.8
diff -u -p -r1.8 sdmmcreg.h
--- dev/sdmmc/sdmmcreg.h 10 Jan 2016 14:11:43 -0000 1.8
+++ dev/sdmmc/sdmmcreg.h 28 Apr 2016 11:24:08 -0000
@@ -41,6 +41,7 @@
/* SD commands */ /* response type */
#define SD_SEND_RELATIVE_ADDR 3 /* R6 */
#define SD_SEND_IF_COND 8 /* R7 */
+#define SD_VOLTAGE_SWITCH 11 /* R1 */
/* SD application commands */ /* response type */
#define SD_APP_SET_BUS_WIDTH 6 /* R1 */
@@ -48,9 +49,11 @@
/* OCR bits */
#define MMC_OCR_MEM_READY (1<<31) /* memory power-up status bit */
+#define MMC_OCR_HCS (1<<30) /* SD only */
#define MMC_OCR_ACCESS_MODE_MASK 0x60000000 /* bits 30:29 */
#define MMC_OCR_SECTOR_MODE (1<<30)
#define MMC_OCR_BYTE_MODE (1<<29)
+#define MMC_OCR_S18A (1<<24)
#define MMC_OCR_3_5V_3_6V (1<<23)
#define MMC_OCR_3_4V_3_5V (1<<22)
#define MMC_OCR_3_3V_3_4V (1<<21)
Index: dev/sdmmc/sdmmcvar.h
===================================================================
RCS file: /cvs/src/sys/dev/sdmmc/sdmmcvar.h,v
retrieving revision 1.22
diff -u -p -r1.22 sdmmcvar.h
--- dev/sdmmc/sdmmcvar.h 12 Sep 2013 11:54:04 -0000 1.22
+++ dev/sdmmc/sdmmcvar.h 28 Apr 2016 11:24:08 -0000
@@ -22,6 +22,8 @@
#include <sys/queue.h>
#include <sys/rwlock.h>
+#include <machine/bus.h>
+
#include <scsi/scsi_all.h>
#include <scsi/scsiconf.h>
@@ -72,6 +74,7 @@ struct sdmmc_command {
u_int16_t c_opcode; /* SD or MMC command index */
u_int32_t c_arg; /* SD/MMC command argument */
sdmmc_response c_resp; /* response buffer */
+ bus_dmamap_t c_dmamap;
void *c_data; /* buffer to send or read into */
int c_datalen; /* length of data buffer */
int c_blklen; /* block length */
@@ -156,6 +159,11 @@ struct sdmmc_softc {
#define DEVNAME(sc) ((sc)->sc_dev.dv_xname)
sdmmc_chipset_tag_t sct; /* host controller chipset tag */
sdmmc_chipset_handle_t sch; /* host controller chipset handle */
+
+ bus_dma_tag_t sc_dmat;
+ bus_dmamap_t sc_dmap;
+#define SDMMC_MAXNSEGS ((MAXPHYS / PAGE_SIZE) + 1)
+
int sc_flags;
#define SMF_SD_MODE 0x0001 /* host in SD mode (MMC otherwise) */
#define SMF_IO_MODE 0x0002 /* host in I/O mode (SD mode only) */
@@ -164,6 +172,7 @@ struct sdmmc_softc {
#define SMF_CARD_ATTACHED 0x0020 /* card driver(s) attached */
#define SMF_STOP_AFTER_MULTIPLE 0x0040 /* send a stop after a multiple
cmd */
#define SMF_CONFIG_PENDING 0x0080 /* config_pending_incr() called */
+#define SMF_UHS_MODE 0x1000 /* host in UHS mode */
uint32_t sc_caps; /* host capability */
#define SMC_CAPS_AUTO_STOP 0x0001 /* send CMD12 automagically by host */
Index: dev/acpi/sdhc_acpi.c
===================================================================
RCS file: /cvs/src/sys/dev/acpi/sdhc_acpi.c,v
retrieving revision 1.7
diff -u -p -r1.7 sdhc_acpi.c
--- dev/acpi/sdhc_acpi.c 26 Apr 2016 19:10:10 -0000 1.7
+++ dev/acpi/sdhc_acpi.c 28 Apr 2016 11:24:08 -0000
@@ -29,6 +29,8 @@
#include <dev/sdmmc/sdhcvar.h>
#include <dev/sdmmc/sdmmcvar.h>
+extern struct bus_dma_tag pci_bus_dma_tag;
+
struct sdhc_acpi_softc {
struct sdhc_softc sc;
struct acpi_softc *sc_acpi;
@@ -134,7 +136,8 @@ sdhc_acpi_attach(struct device *parent,
printf("\n");
sc->sc.sc_host = &sc->sc_host;
- sdhc_host_found(&sc->sc, sc->sc_memt, sc->sc_memh, sc->sc_size, 0, 0);
+ sc->sc.sc_dmat = &pci_bus_dma_tag;
+ sdhc_host_found(&sc->sc, sc->sc_memt, sc->sc_memh, sc->sc_size, 1, 0);
}
int
Index: dev/ic/rtsx.c
===================================================================
RCS file: /cvs/src/sys/dev/ic/rtsx.c,v
retrieving revision 1.12
diff -u -p -r1.12 rtsx.c
--- dev/ic/rtsx.c 28 Apr 2015 07:55:13 -0000 1.12
+++ dev/ic/rtsx.c 28 Apr 2016 11:24:09 -0000
@@ -146,6 +146,7 @@ struct sdmmc_chip_functions rtsx_functio
/* bus power and clock frequency */
rtsx_bus_power,
rtsx_bus_clock,
+ NULL,
/* command execution */
rtsx_exec_command,
/* card interrupt */
Index: dev/pci/sdhc_pci.c
===================================================================
RCS file: /cvs/src/sys/dev/pci/sdhc_pci.c,v
retrieving revision 1.19
diff -u -p -r1.19 sdhc_pci.c
--- dev/pci/sdhc_pci.c 24 Nov 2015 19:38:01 -0000 1.19
+++ dev/pci/sdhc_pci.c 28 Apr 2016 11:24:09 -0000
@@ -149,6 +149,7 @@ sdhc_pci_attach(struct device *parent, s
/* Enable use of DMA if supported by the interface. */
usedma = PCI_INTERFACE(pa->pa_class) == SDHC_PCI_INTERFACE_DMA;
+ sc->sc.sc_dmat = pa->pa_dmat;
/*
* Map and attach all hosts supported by the host controller.
Index: arch/armv7/exynos/exesdhc.c
===================================================================
RCS file: /cvs/src/sys/arch/armv7/exynos/exesdhc.c,v
retrieving revision 1.5
diff -u -p -r1.5 exesdhc.c
--- arch/armv7/exynos/exesdhc.c 24 Apr 2016 00:57:23 -0000 1.5
+++ arch/armv7/exynos/exesdhc.c 28 Apr 2016 11:24:09 -0000
@@ -228,6 +228,7 @@ struct sdmmc_chip_functions exesdhc_func
/* bus power and clock frequency */
exesdhc_bus_power,
exesdhc_bus_clock,
+ NULL,
/* command execution */
exesdhc_exec_command,
/* card interrupt */
Index: arch/armv7/imx/imxesdhc.c
===================================================================
RCS file: /cvs/src/sys/arch/armv7/imx/imxesdhc.c,v
retrieving revision 1.13
diff -u -p -r1.13 imxesdhc.c
--- arch/armv7/imx/imxesdhc.c 10 Jan 2016 14:11:43 -0000 1.13
+++ arch/armv7/imx/imxesdhc.c 28 Apr 2016 11:24:09 -0000
@@ -226,6 +226,7 @@ struct sdmmc_chip_functions imxesdhc_fun
/* bus power and clock frequency */
imxesdhc_bus_power,
imxesdhc_bus_clock,
+ NULL,
/* command execution */
imxesdhc_exec_command,
/* card interrupt */
Index: arch/armv7/omap/ommmc.c
===================================================================
RCS file: /cvs/src/sys/arch/armv7/omap/ommmc.c,v
retrieving revision 1.15
diff -u -p -r1.15 ommmc.c
--- arch/armv7/omap/ommmc.c 10 Jan 2016 14:11:43 -0000 1.15
+++ arch/armv7/omap/ommmc.c 28 Apr 2016 11:24:09 -0000
@@ -260,6 +260,7 @@ struct sdmmc_chip_functions ommmc_functi
/* bus power and clock frequency */
ommmc_bus_power,
ommmc_bus_clock,
+ NULL,
/* command execution */
ommmc_exec_command,
/* card interrupt */