| @@ -1,14 +1,14 @@ | | | @@ -1,14 +1,14 @@ |
1 | /* $NetBSD: bcm2835_emmc.c,v 1.10 2014/09/10 13:45:52 jakllsch Exp $ */ | | 1 | /* $NetBSD: bcm2835_emmc.c,v 1.11 2014/09/12 20:18:42 jakllsch Exp $ */ |
2 | | | 2 | |
3 | /*- | | 3 | /*- |
4 | * Copyright (c) 2012 The NetBSD Foundation, Inc. | | 4 | * Copyright (c) 2012 The NetBSD Foundation, Inc. |
5 | * All rights reserved. | | 5 | * All rights reserved. |
6 | * | | 6 | * |
7 | * This code is derived from software contributed to The NetBSD Foundation | | 7 | * This code is derived from software contributed to The NetBSD Foundation |
8 | * by Nick Hudson | | 8 | * by Nick Hudson |
9 | * | | 9 | * |
10 | * Redistribution and use in source and binary forms, with or without | | 10 | * Redistribution and use in source and binary forms, with or without |
11 | * modification, are permitted provided that the following conditions | | 11 | * modification, are permitted provided that the following conditions |
12 | * are met: | | 12 | * are met: |
13 | * 1. Redistributions of source code must retain the above copyright | | 13 | * 1. Redistributions of source code must retain the above copyright |
14 | * notice, this list of conditions and the following disclaimer. | | 14 | * notice, this list of conditions and the following disclaimer. |
| @@ -20,123 +20,286 @@ | | | @@ -20,123 +20,286 @@ |
20 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED | | 20 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
21 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | | 21 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
22 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS | | 22 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS |
23 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | | 23 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | | 24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | | 25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | | 26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | | 27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | | 28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
29 | * POSSIBILITY OF SUCH DAMAGE. | | 29 | * POSSIBILITY OF SUCH DAMAGE. |
30 | */ | | 30 | */ |
31 | | | 31 | |
32 | #include <sys/cdefs.h> | | 32 | #include <sys/cdefs.h> |
33 | __KERNEL_RCSID(0, "$NetBSD: bcm2835_emmc.c,v 1.10 2014/09/10 13:45:52 jakllsch Exp $"); | | 33 | __KERNEL_RCSID(0, "$NetBSD: bcm2835_emmc.c,v 1.11 2014/09/12 20:18:42 jakllsch Exp $"); |
34 | | | 34 | |
35 | #include <sys/param.h> | | 35 | #include <sys/param.h> |
36 | #include <sys/systm.h> | | 36 | #include <sys/systm.h> |
37 | #include <sys/device.h> | | 37 | #include <sys/device.h> |
38 | #include <sys/bus.h> | | 38 | #include <sys/bus.h> |
| | | 39 | #include <sys/condvar.h> |
| | | 40 | #include <sys/mutex.h> |
39 | | | 41 | |
40 | #include <arm/broadcom/bcm2835reg.h> | | 42 | #include <arm/broadcom/bcm2835reg.h> |
41 | #include <arm/broadcom/bcm_amba.h> | | 43 | #include <arm/broadcom/bcm_amba.h> |
| | | 44 | #include <arm/broadcom/bcm2835_dmac.h> |
42 | | | 45 | |
43 | #include <dev/sdmmc/sdhcreg.h> | | 46 | #include <dev/sdmmc/sdhcreg.h> |
44 | #include <dev/sdmmc/sdhcvar.h> | | 47 | #include <dev/sdmmc/sdhcvar.h> |
45 | #include <dev/sdmmc/sdmmcvar.h> | | 48 | #include <dev/sdmmc/sdmmcvar.h> |
46 | | | 49 | |
| | | 50 | enum bcmemmc_dma_state { |
| | | 51 | EMMC_DMA_STATE_IDLE = 27, |
| | | 52 | EMMC_DMA_STATE_BUSY = 42, |
| | | 53 | }; |
| | | 54 | |
47 | struct bcmemmc_softc { | | 55 | struct bcmemmc_softc { |
48 | struct sdhc_softc sc; | | 56 | struct sdhc_softc sc; |
49 | | | 57 | |
50 | bus_space_tag_t sc_iot; | | 58 | bus_space_tag_t sc_iot; |
51 | bus_space_handle_t sc_ioh; | | 59 | bus_space_handle_t sc_ioh; |
| | | 60 | bus_size_t sc_ios; |
52 | struct sdhc_host *sc_hosts[1]; | | 61 | struct sdhc_host *sc_hosts[1]; |
53 | void *sc_ih; | | 62 | void *sc_ih; |
| | | 63 | |
| | | 64 | kmutex_t sc_lock; |
| | | 65 | kcondvar_t sc_cv; |
| | | 66 | |
| | | 67 | enum bcmemmc_dma_state sc_state; |
| | | 68 | |
| | | 69 | struct bcm_dmac_channel *sc_dmac; |
| | | 70 | |
| | | 71 | bus_dmamap_t sc_dmamap; |
| | | 72 | bus_dma_segment_t sc_segs[1]; /* XXX assumes enough descriptors fit in one page */ |
| | | 73 | struct bcm_dmac_conblk *sc_cblk; |
| | | 74 | |
| | | 75 | uint32_t sc_physaddr; |
54 | }; | | 76 | }; |
55 | | | 77 | |
56 | static int bcmemmc_match(device_t, struct cfdata *, void *); | | 78 | static int bcmemmc_match(device_t, struct cfdata *, void *); |
57 | static void bcmemmc_attach(device_t, device_t, void *); | | 79 | static void bcmemmc_attach(device_t, device_t, void *); |
| | | 80 | static void bcmemmc_attach_i(device_t); |
| | | 81 | static int bcmemmc_xfer_data_dma(struct sdhc_host *, struct sdmmc_command *); |
| | | 82 | static void bcmemmc_dma_done(void *); |
58 | | | 83 | |
59 | CFATTACH_DECL_NEW(bcmemmc, sizeof(struct bcmemmc_softc), | | 84 | CFATTACH_DECL_NEW(bcmemmc, sizeof(struct bcmemmc_softc), |
60 | bcmemmc_match, bcmemmc_attach, NULL, NULL); | | 85 | bcmemmc_match, bcmemmc_attach, NULL, NULL); |
61 | | | 86 | |
62 | /* ARGSUSED */ | | 87 | /* ARGSUSED */ |
63 | static int | | 88 | static int |
64 | bcmemmc_match(device_t parent, struct cfdata *match, void *aux) | | 89 | bcmemmc_match(device_t parent, struct cfdata *match, void *aux) |
65 | { | | 90 | { |
66 | struct amba_attach_args *aaa = aux; | | 91 | struct amba_attach_args *aaa = aux; |
67 | | | 92 | |
68 | if (strcmp(aaa->aaa_name, "emmc") != 0) | | 93 | if (strcmp(aaa->aaa_name, "emmc") != 0) |
69 | return 0; | | 94 | return 0; |
70 | | | 95 | |
71 | return 1; | | 96 | return 1; |
72 | } | | 97 | } |
73 | | | 98 | |
74 | /* ARGSUSED */ | | 99 | /* ARGSUSED */ |
75 | static void | | 100 | static void |
76 | bcmemmc_attach(device_t parent, device_t self, void *aux) | | 101 | bcmemmc_attach(device_t parent, device_t self, void *aux) |
77 | { | | 102 | { |
78 | struct bcmemmc_softc *sc = device_private(self); | | 103 | struct bcmemmc_softc *sc = device_private(self); |
79 | prop_dictionary_t dict = device_properties(self); | | 104 | prop_dictionary_t dict = device_properties(self); |
80 | struct amba_attach_args *aaa = aux; | | 105 | struct amba_attach_args *aaa = aux; |
81 | prop_number_t frequency; | | 106 | prop_number_t frequency; |
82 | int error; | | 107 | int error; |
| | | 108 | int rseg; |
83 | | | 109 | |
84 | sc->sc.sc_dev = self; | | 110 | sc->sc.sc_dev = self; |
85 | sc->sc.sc_dmat = aaa->aaa_dmat; | | 111 | sc->sc.sc_dmat = aaa->aaa_dmat; |
86 | sc->sc.sc_flags = 0; | | 112 | sc->sc.sc_flags = 0; |
87 | sc->sc.sc_flags |= SDHC_FLAG_32BIT_ACCESS; | | 113 | sc->sc.sc_flags |= SDHC_FLAG_32BIT_ACCESS; |
88 | sc->sc.sc_flags |= SDHC_FLAG_HOSTCAPS; | | 114 | sc->sc.sc_flags |= SDHC_FLAG_HOSTCAPS; |
89 | sc->sc.sc_flags |= SDHC_FLAG_NO_HS_BIT; | | 115 | sc->sc.sc_flags |= SDHC_FLAG_NO_HS_BIT; |
90 | sc->sc.sc_caps = SDHC_VOLTAGE_SUPP_3_3V | SDHC_HIGH_SPEED_SUPP | | | 116 | sc->sc.sc_caps = SDHC_VOLTAGE_SUPP_3_3V | SDHC_HIGH_SPEED_SUPP | |
91 | SDHC_MAX_BLK_LEN_1024; | | 117 | SDHC_MAX_BLK_LEN_1024; |
92 | #if notyet | | | |
93 | sc->sc.sc_flags |= SDHC_FLAG_USE_DMA; | | 118 | sc->sc.sc_flags |= SDHC_FLAG_USE_DMA; |
| | | 119 | sc->sc.sc_flags |= SDHC_FLAG_EXTERNAL_DMA; |
94 | sc->sc.sc_caps |= SDHC_DMA_SUPPORT; | | 120 | sc->sc.sc_caps |= SDHC_DMA_SUPPORT; |
95 | #endif | | 121 | |
96 | sc->sc.sc_host = sc->sc_hosts; | | 122 | sc->sc.sc_host = sc->sc_hosts; |
97 | sc->sc.sc_clkbase = 50000; /* Default to 50MHz */ | | 123 | sc->sc.sc_clkbase = 50000; /* Default to 50MHz */ |
98 | sc->sc_iot = aaa->aaa_iot; | | 124 | sc->sc_iot = aaa->aaa_iot; |
| | | 125 | sc->sc.sc_vendor_transfer_data_dma = bcmemmc_xfer_data_dma; |
99 | | | 126 | |
100 | /* Fetch the EMMC clock frequency from property if set. */ | | 127 | /* Fetch the EMMC clock frequency from property if set. */ |
101 | frequency = prop_dictionary_get(dict, "frequency"); | | 128 | frequency = prop_dictionary_get(dict, "frequency"); |
102 | if (frequency != NULL) { | | 129 | if (frequency != NULL) { |
103 | sc->sc.sc_clkbase = prop_number_integer_value(frequency) / 1000; | | 130 | sc->sc.sc_clkbase = prop_number_integer_value(frequency) / 1000; |
104 | } | | 131 | } |
105 | | | 132 | |
106 | error = bus_space_map(sc->sc_iot, aaa->aaa_addr, aaa->aaa_size, 0, | | 133 | error = bus_space_map(sc->sc_iot, aaa->aaa_addr, aaa->aaa_size, 0, |
107 | &sc->sc_ioh); | | 134 | &sc->sc_ioh); |
108 | if (error) { | | 135 | if (error) { |
109 | aprint_error_dev(self, | | 136 | aprint_error_dev(self, |
110 | "can't map registers for %s: %d\n", aaa->aaa_name, error); | | 137 | "can't map registers for %s: %d\n", aaa->aaa_name, error); |
111 | return; | | 138 | return; |
112 | } | | 139 | } |
| | | 140 | sc->sc_ios = aaa->aaa_size; |
| | | 141 | sc->sc_physaddr = aaa->aaa_addr; |
113 | | | 142 | |
114 | aprint_naive(": SDHC controller\n"); | | 143 | aprint_naive(": SDHC controller\n"); |
115 | aprint_normal(": SDHC controller\n"); | | 144 | aprint_normal(": SDHC controller\n"); |
116 | | | 145 | |
117 | sc->sc_ih = bcm2835_intr_establish(aaa->aaa_intr, IPL_SDMMC, sdhc_intr, | | 146 | sc->sc_ih = bcm2835_intr_establish(aaa->aaa_intr, IPL_SDMMC, sdhc_intr, |
118 | &sc->sc); | | 147 | &sc->sc); |
119 | | | 148 | |
120 | if (sc->sc_ih == NULL) { | | 149 | if (sc->sc_ih == NULL) { |
121 | aprint_error_dev(self, "failed to establish interrupt %d\n", | | 150 | aprint_error_dev(self, "failed to establish interrupt %d\n", |
122 | aaa->aaa_intr); | | 151 | aaa->aaa_intr); |
123 | goto fail; | | 152 | goto fail; |
124 | } | | 153 | } |
125 | aprint_normal_dev(self, "interrupting on intr %d\n", aaa->aaa_intr); | | 154 | aprint_normal_dev(self, "interrupting on intr %d\n", aaa->aaa_intr); |
126 | | | 155 | |
127 | error = sdhc_host_found(&sc->sc, sc->sc_iot, sc->sc_ioh, | | 156 | sc->sc_dmac = bcm_dmac_alloc(BCM_DMAC_TYPE_NORMAL, IPL_SDMMC, |
128 | aaa->aaa_size); | | 157 | bcmemmc_dma_done, sc); |
| | | 158 | if (sc->sc_dmac == NULL) |
| | | 159 | goto fail; |
| | | 160 | |
| | | 161 | sc->sc_state = EMMC_DMA_STATE_IDLE; |
| | | 162 | mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_SDMMC); |
| | | 163 | cv_init(&sc->sc_cv, "bcmemmcdma"); |
| | | 164 | |
| | | 165 | error = bus_dmamem_alloc(sc->sc.sc_dmat, PAGE_SIZE, PAGE_SIZE, |
| | | 166 | PAGE_SIZE, sc->sc_segs, 1, &rseg, BUS_DMA_WAITOK); |
| | | 167 | if (error) { |
| | | 168 | aprint_error_dev(self, "dmamem_alloc failed (%d)\n", error); |
| | | 169 | goto fail; |
| | | 170 | } |
| | | 171 | |
| | | 172 | error = bus_dmamem_map(sc->sc.sc_dmat, sc->sc_segs, rseg, PAGE_SIZE, |
| | | 173 | (void **)&sc->sc_cblk, BUS_DMA_WAITOK); |
| | | 174 | if (error) { |
| | | 175 | aprint_error_dev(self, "dmamem_map failed (%d)\n", error); |
| | | 176 | goto fail; |
| | | 177 | } |
| | | 178 | KASSERT(sc->sc_cblk != NULL); |
| | | 179 | |
| | | 180 | memset(sc->sc_cblk, 0, PAGE_SIZE); |
| | | 181 | |
| | | 182 | error = bus_dmamap_create(sc->sc.sc_dmat, PAGE_SIZE, 1, PAGE_SIZE, 0, |
| | | 183 | BUS_DMA_WAITOK, &sc->sc_dmamap); |
| | | 184 | if (error) { |
| | | 185 | aprint_error_dev(self, "dmamap_create failed (%d)\n", error); |
| | | 186 | goto fail; |
| | | 187 | } |
| | | 188 | |
| | | 189 | error = bus_dmamap_load(sc->sc.sc_dmat, sc->sc_dmamap, sc->sc_cblk, |
| | | 190 | PAGE_SIZE, NULL, BUS_DMA_WAITOK|BUS_DMA_WRITE); |
| | | 191 | if (error) { |
| | | 192 | aprint_error_dev(self, "dmamap_load failed (%d)\n", error); |
| | | 193 | goto fail; |
| | | 194 | } |
| | | 195 | |
| | | 196 | config_interrupts(self, bcmemmc_attach_i); |
| | | 197 | return; |
| | | 198 | |
| | | 199 | fail: |
| | | 200 | /* XXX add bus_dma failure cleanup */ |
| | | 201 | if (sc->sc_ih) { |
| | | 202 | intr_disestablish(sc->sc_ih); |
| | | 203 | sc->sc_ih = NULL; |
| | | 204 | } |
| | | 205 | bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_ios); |
| | | 206 | } |
| | | 207 | |
| | | 208 | static void |
| | | 209 | bcmemmc_attach_i(device_t self) |
| | | 210 | { |
| | | 211 | struct bcmemmc_softc * const sc = device_private(self); |
| | | 212 | int error; |
| | | 213 | |
| | | 214 | error = sdhc_host_found(&sc->sc, sc->sc_iot, sc->sc_ioh, sc->sc_ios); |
129 | if (error != 0) { | | 215 | if (error != 0) { |
130 | aprint_error_dev(self, "couldn't initialize host, error=%d\n", | | 216 | aprint_error_dev(self, "couldn't initialize host, error=%d\n", |
131 | error); | | 217 | error); |
132 | goto fail; | | 218 | goto fail; |
133 | } | | 219 | } |
134 | return; | | 220 | return; |
135 | | | 221 | |
136 | fail: | | 222 | fail: |
| | | 223 | /* XXX add bus_dma failure cleanup */ |
137 | if (sc->sc_ih) { | | 224 | if (sc->sc_ih) { |
138 | intr_disestablish(sc->sc_ih); | | 225 | intr_disestablish(sc->sc_ih); |
139 | sc->sc_ih = NULL; | | 226 | sc->sc_ih = NULL; |
140 | } | | 227 | } |
141 | bus_space_unmap(sc->sc_iot, sc->sc_ioh, aaa->aaa_size); | | 228 | bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_ios); |
| | | 229 | } |
| | | 230 | |
| | | 231 | static int |
| | | 232 | bcmemmc_xfer_data_dma(struct sdhc_host *hp, struct sdmmc_command *cmd) |
| | | 233 | { |
| | | 234 | struct bcmemmc_softc * const sc = *(void **)hp; /* XXX XXX XXX */ |
| | | 235 | size_t seg; |
| | | 236 | |
| | | 237 | for (seg = 0; seg < cmd->c_dmamap->dm_nsegs; seg++) { |
| | | 238 | sc->sc_cblk[seg].cb_ti = |
| | | 239 | __SHIFTIN(11, DMAC_TI_PERMAP); /* e.MMC */ |
| | | 240 | if (ISSET(cmd->c_flags, SCF_CMD_READ)) { |
| | | 241 | sc->sc_cblk[seg].cb_ti |= DMAC_TI_DEST_INC; |
| | | 242 | sc->sc_cblk[seg].cb_ti |= DMAC_TI_DEST_WIDTH; |
| | | 243 | sc->sc_cblk[seg].cb_ti |= DMAC_TI_SRC_DREQ; |
| | | 244 | sc->sc_cblk[seg].cb_source_ad = |
| | | 245 | BCM2835_PERIPHERALS_TO_BUS(sc->sc_physaddr + |
| | | 246 | SDHC_DATA); |
| | | 247 | sc->sc_cblk[seg].cb_dest_ad = |
| | | 248 | cmd->c_dmamap->dm_segs[seg].ds_addr; |
| | | 249 | } else { |
| | | 250 | sc->sc_cblk[seg].cb_ti |= DMAC_TI_SRC_INC; |
| | | 251 | sc->sc_cblk[seg].cb_ti |= DMAC_TI_SRC_WIDTH; |
| | | 252 | sc->sc_cblk[seg].cb_ti |= DMAC_TI_DEST_DREQ; |
| | | 253 | sc->sc_cblk[seg].cb_source_ad = |
| | | 254 | cmd->c_dmamap->dm_segs[seg].ds_addr; |
| | | 255 | sc->sc_cblk[seg].cb_dest_ad = |
| | | 256 | BCM2835_PERIPHERALS_TO_BUS(sc->sc_physaddr + |
| | | 257 | SDHC_DATA); |
| | | 258 | } |
| | | 259 | sc->sc_cblk[seg].cb_txfr_len = |
| | | 260 | cmd->c_dmamap->dm_segs[seg].ds_len; |
| | | 261 | sc->sc_cblk[seg].cb_stride = 0; |
| | | 262 | if (seg == cmd->c_dmamap->dm_nsegs - 1) { |
| | | 263 | sc->sc_cblk[seg].cb_ti |= DMAC_TI_WAIT_RESP; |
| | | 264 | sc->sc_cblk[seg].cb_ti |= DMAC_TI_INTEN; |
| | | 265 | sc->sc_cblk[seg].cb_nextconbk = 0; |
| | | 266 | } else { |
| | | 267 | sc->sc_cblk[seg].cb_nextconbk = |
| | | 268 | sc->sc_dmamap->dm_segs[0].ds_addr + |
| | | 269 | sizeof(struct bcm_dmac_conblk) * (seg+1); |
| | | 270 | } |
| | | 271 | sc->sc_cblk[seg].cb_padding[0] = 0; |
| | | 272 | sc->sc_cblk[seg].cb_padding[1] = 0; |
| | | 273 | } |
| | | 274 | |
| | | 275 | bus_dmamap_sync(sc->sc.sc_dmat, sc->sc_dmamap, 0, |
| | | 276 | sc->sc_dmamap->dm_mapsize, BUS_DMASYNC_PREWRITE); |
| | | 277 | |
| | | 278 | mutex_enter(&sc->sc_lock); |
| | | 279 | KASSERT(sc->sc_state == EMMC_DMA_STATE_IDLE); |
| | | 280 | sc->sc_state = EMMC_DMA_STATE_BUSY; |
| | | 281 | bcm_dmac_set_conblk_addr(sc->sc_dmac, |
| | | 282 | sc->sc_dmamap->dm_segs[0].ds_addr); |
| | | 283 | bcm_dmac_transfer(sc->sc_dmac); |
| | | 284 | while (sc->sc_state == EMMC_DMA_STATE_BUSY) |
| | | 285 | cv_wait(&sc->sc_cv, &sc->sc_lock); |
| | | 286 | mutex_exit(&sc->sc_lock); |
| | | 287 | |
| | | 288 | bus_dmamap_sync(sc->sc.sc_dmat, sc->sc_dmamap, 0, |
| | | 289 | sc->sc_dmamap->dm_mapsize, BUS_DMASYNC_POSTWRITE); |
| | | 290 | |
| | | 291 | return 0; |
| | | 292 | } |
| | | 293 | |
| | | 294 | static void |
| | | 295 | bcmemmc_dma_done(void *arg) |
| | | 296 | { |
| | | 297 | struct bcmemmc_softc * const sc = arg; |
| | | 298 | |
| | | 299 | mutex_enter(&sc->sc_lock); |
| | | 300 | KASSERT(sc->sc_state == EMMC_DMA_STATE_BUSY); |
| | | 301 | sc->sc_state = EMMC_DMA_STATE_IDLE; |
| | | 302 | cv_broadcast(&sc->sc_cv); |
| | | 303 | mutex_exit(&sc->sc_lock); |
| | | 304 | |
142 | } | | 305 | } |