From owner-svn-src-head@FreeBSD.ORG Tue Apr 28 08:27:45 2015 Return-Path: Delivered-To: svn-src-head@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:1900:2254:206a::19:1]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by hub.freebsd.org (Postfix) with ESMTPS id 1DF2A896; Tue, 28 Apr 2015 08:27:45 +0000 (UTC) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:1900:2254:2068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mx1.freebsd.org (Postfix) with ESMTPS id 0B94B1920; Tue, 28 Apr 2015 08:27:45 +0000 (UTC) Received: from svn.freebsd.org ([127.0.1.70]) by svn.freebsd.org (8.14.9/8.14.9) with ESMTP id t3S8Rj7V030228; Tue, 28 Apr 2015 08:27:45 GMT (envelope-from ganbold@FreeBSD.org) Received: (from ganbold@localhost) by svn.freebsd.org (8.14.9/8.14.9/Submit) id t3S8RiaD030225; Tue, 28 Apr 2015 08:27:44 GMT (envelope-from ganbold@FreeBSD.org) Message-Id: <201504280827.t3S8RiaD030225@svn.freebsd.org> X-Authentication-Warning: svn.freebsd.org: ganbold set sender to ganbold@FreeBSD.org using -f From: Ganbold Tsagaankhuu Date: Tue, 28 Apr 2015 08:27:44 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r282129 - head/sys/arm/amlogic/aml8726 X-SVN-Group: head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-head@freebsd.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: SVN commit messages for the src tree for head/-current List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 28 Apr 2015 08:27:45 -0000 Author: ganbold Date: Tue Apr 28 08:27:44 2015 New Revision: 282129 URL: https://svnweb.freebsd.org/changeset/base/282129 Log: Update Amlogic MMC driver: 1) Advertise the actual min / max speeds the hardware is capable of supporting given the reference clock used by the board. 2) Rather than attempting to extend the hardware's timeout register in software (the hardware doesn't have sufficient bits to directly support long timeouts), simply implement the same timeout approach used in the SDXC driver. 3) Set the timeout for a linked command (e.g. STOP TRANSMISSION) based on the previous multiblock read / write. The changes have been smoke tested on both the ODROID-C1 and the VSATV102-M6 using the following cards: * PQI 2GB microSD * SanDisk 2GB microSD * PQI 8GB SDHC (not a microSD so only tested on the ATV-102) * PNY 8GB microSDHC * SanDisk Ultra 32GB microSDHC Submitted by: John Wehle Modified: head/sys/arm/amlogic/aml8726/aml8726_mmc.c head/sys/arm/amlogic/aml8726/aml8726_mmc.h Modified: head/sys/arm/amlogic/aml8726/aml8726_mmc.c ============================================================================== --- head/sys/arm/amlogic/aml8726/aml8726_mmc.c Tue Apr 28 08:20:23 2015 (r282128) +++ head/sys/arm/amlogic/aml8726/aml8726_mmc.c Tue Apr 28 08:27:44 2015 (r282129) @@ -70,6 +70,7 @@ struct aml8726_mmc_softc { device_t dev; struct resource *res[2]; struct mtx mtx; + struct callout ch; uint32_t port; unsigned int ref_freq; struct aml8726_mmc_gpio pwr_en; @@ -81,7 +82,7 @@ struct aml8726_mmc_softc { struct mmc_host host; int bus_busy; struct mmc_command *cmd; - unsigned int timeout_remaining; + uint32_t stop_timeout; }; static struct resource_spec aml8726_mmc_spec[] = { @@ -92,6 +93,7 @@ static struct resource_spec aml8726_mmc_ #define AML_MMC_LOCK(sc) mtx_lock(&(sc)->mtx) #define AML_MMC_UNLOCK(sc) mtx_unlock(&(sc)->mtx) +#define AML_MMC_LOCK_ASSERT(sc) mtx_assert(&(sc)->mtx, MA_OWNED) #define AML_MMC_LOCK_INIT(sc) \ mtx_init(&(sc)->mtx, device_get_nameunit((sc)->dev), \ "mmc", MTX_DEF) @@ -107,6 +109,10 @@ static struct resource_spec aml8726_mmc_ #define PWR_OFF_FLAG(pol) ((pol) == 0 ? GPIO_PIN_HIGH : \ GPIO_PIN_LOW) +#define MSECS_TO_TICKS(ms) (((ms)*hz)/1000 + 1) + +static void aml8726_mmc_timeout(void *arg); + static unsigned int aml8726_mmc_clk(phandle_t node) { @@ -126,6 +132,37 @@ aml8726_mmc_clk(phandle_t node) return ((unsigned int)prop); } +static uint32_t +aml8726_mmc_freq(struct aml8726_mmc_softc *sc, uint32_t divisor) +{ + + return (sc->ref_freq / ((divisor + 1) * 2)); +} + +static uint32_t +aml8726_mmc_div(struct aml8726_mmc_softc *sc, uint32_t desired_freq) +{ + uint32_t divisor; + + divisor = sc->ref_freq / (desired_freq * 2); + + if (divisor == 0) + divisor = 1; + + divisor -= 1; + + if (aml8726_mmc_freq(sc, divisor) > desired_freq) + divisor += 1; + + if (divisor > (AML_MMC_CONFIG_CMD_CLK_DIV_MASK >> + AML_MMC_CONFIG_CMD_CLK_DIV_SHIFT)) { + divisor = AML_MMC_CONFIG_CMD_CLK_DIV_MASK >> + AML_MMC_CONFIG_CMD_CLK_DIV_SHIFT; + } + + return (divisor); +} + static void aml8726_mmc_mapmem(void *arg, bus_dma_segment_t *segs, int nseg, int error) { @@ -162,25 +199,18 @@ aml8726_mmc_power_on(struct aml8726_mmc_ PWR_ON_FLAG(sc->pwr_en.pol))); } -static int -aml8726_mmc_restart_timer(struct aml8726_mmc_softc *sc) +static void +aml8726_mmc_soft_reset(struct aml8726_mmc_softc *sc, boolean_t enable_irq) { - uint32_t count; - uint32_t isr; - - if (sc->cmd == NULL || sc->timeout_remaining == 0) - return (0); - - count = (sc->timeout_remaining > 0x1fff) ? 0x1fff : - sc->timeout_remaining; - sc->timeout_remaining -= count; + uint32_t icr; - isr = (count << AML_MMC_IRQ_STATUS_TIMER_CNT_SHIFT) | - AML_MMC_IRQ_STATUS_TIMER_EN | AML_MMC_IRQ_STATUS_TIMEOUT_IRQ; + icr = AML_MMC_IRQ_CONFIG_SOFT_RESET; - CSR_WRITE_4(sc, AML_MMC_IRQ_STATUS_REG, isr); + if (enable_irq == true) + icr |= AML_MMC_IRQ_CONFIG_CMD_DONE_EN; - return (1); + CSR_WRITE_4(sc, AML_MMC_IRQ_CONFIG_REG, icr); + CSR_BARRIER(sc, AML_MMC_IRQ_CONFIG_REG); } static int @@ -191,7 +221,6 @@ aml8726_mmc_start_command(struct aml8726 uint32_t block_size; uint32_t bus_width; uint32_t cmdr; - uint32_t cycles_per_msec; uint32_t extr; uint32_t mcfgr; uint32_t nbits_per_pkg; @@ -203,14 +232,9 @@ aml8726_mmc_start_command(struct aml8726 return (MMC_ERR_INVALID); /* - * Ensure the hardware state machine is in a known state, - * the command done interrupt is enabled, and previous - * IRQ status bits have been cleared. + * Ensure the hardware state machine is in a known state. */ - CSR_WRITE_4(sc, AML_MMC_IRQ_CONFIG_REG, - (AML_MMC_IRQ_CONFIG_SOFT_RESET | AML_MMC_IRQ_CONFIG_CMD_DONE_EN)); - CSR_BARRIER(sc, AML_MMC_IRQ_CONFIG_REG); - CSR_WRITE_4(sc, AML_MMC_IRQ_STATUS_REG, AML_MMC_IRQ_STATUS_CLEAR_IRQ); + aml8726_mmc_soft_reset(sc, true); /* * Start and transmission bits are per section 4.7.2 of the: @@ -225,6 +249,13 @@ aml8726_mmc_start_command(struct aml8726 mcfgr = sc->port; timeout = AML_MMC_CMD_TIMEOUT; + /* + * If this is a linked command, then use the previous timeout. + */ + if (cmd == cmd->mrq->stop && sc->stop_timeout) + timeout = sc->stop_timeout; + sc->stop_timeout = 0; + if ((cmd->flags & MMC_RSP_136) != 0) { cmdr |= AML_MMC_CMD_RESP_CRC7_FROM_8; cmdr |= (133 << AML_MMC_CMD_RESP_BITS_SHIFT); @@ -291,32 +322,24 @@ aml8726_mmc_start_command(struct aml8726 timeout = AML_MMC_WRITE_TIMEOUT * (data->len / block_size); } + + /* + * Stop terminates a multiblock read / write and thus + * can take as long to execute as an actual read / write. + */ + if (cmd->mrq->stop != NULL) + sc->stop_timeout = timeout; } sc->cmd = cmd; cmd->error = MMC_ERR_NONE; - /* - * Round up while calculating the number of cycles which - * correspond to a millisecond. Use that to determine - * the count from the desired timeout in milliseconds. - * - * The counter has a limited range which is not sufficient - * for directly implementing worst case timeouts at high clock - * rates so a 32 bit counter is implemented in software. - * - * The documentation isn't clear on when the timer starts - * so add 48 cycles for the command and 136 cycles for the - * response (the values are from the previously mentioned - * standard). - */ if (timeout > AML_MMC_MAX_TIMEOUT) timeout = AML_MMC_MAX_TIMEOUT; - cycles_per_msec = (ios->clock + 1000 - 1) / 1000; - sc->timeout_remaining = 48 + 136 + timeout * cycles_per_msec; - aml8726_mmc_restart_timer(sc); + callout_reset(&sc->ch, MSECS_TO_TICKS(timeout), + aml8726_mmc_timeout, sc); CSR_WRITE_4(sc, AML_MMC_CMD_ARGUMENT_REG, cmd->arg); CSR_WRITE_4(sc, AML_MMC_MULT_CONFIG_REG, mcfgr); @@ -330,20 +353,96 @@ aml8726_mmc_start_command(struct aml8726 } static void -aml8726_mmc_intr(void *arg) +aml8726_mmc_finish_command(struct aml8726_mmc_softc *sc, int mmc_error) { - struct aml8726_mmc_softc *sc = (struct aml8726_mmc_softc *)arg; + int mmc_stop_error; struct mmc_command *cmd; struct mmc_command *stop_cmd; struct mmc_data *data; + + AML_MMC_LOCK_ASSERT(sc); + + /* Clear all interrupts since the request is no longer in flight. */ + CSR_WRITE_4(sc, AML_MMC_IRQ_STATUS_REG, AML_MMC_IRQ_STATUS_CLEAR_IRQ); + CSR_BARRIER(sc, AML_MMC_IRQ_STATUS_REG); + + /* In some cases (e.g. finish called via timeout) this is a NOP. */ + callout_stop(&sc->ch); + + cmd = sc->cmd; + sc->cmd = NULL; + + cmd->error = mmc_error; + + data = cmd->data; + + if (data && data->len && + (data->flags & (MMC_DATA_READ | MMC_DATA_WRITE)) != 0) { + if ((data->flags & MMC_DATA_READ) != 0) + bus_dmamap_sync(sc->dmatag, sc->dmamap, + BUS_DMASYNC_POSTREAD); + else + bus_dmamap_sync(sc->dmatag, sc->dmamap, + BUS_DMASYNC_POSTWRITE); + bus_dmamap_unload(sc->dmatag, sc->dmamap); + } + + /* + * If there's a linked stop command, then start the stop command. + * In order to establish a known state attempt the stop command + * even if the original request encountered an error. + */ + + stop_cmd = (cmd->mrq->stop != cmd) ? cmd->mrq->stop : NULL; + + if (stop_cmd != NULL) { + mmc_stop_error = aml8726_mmc_start_command(sc, stop_cmd); + if (mmc_stop_error == MMC_ERR_NONE) { + AML_MMC_UNLOCK(sc); + return; + } + stop_cmd->error = mmc_stop_error; + } + + AML_MMC_UNLOCK(sc); + + /* Execute the callback after dropping the lock. */ + if (cmd->mrq) + cmd->mrq->done(cmd->mrq); +} + +static void +aml8726_mmc_timeout(void *arg) +{ + struct aml8726_mmc_softc *sc = (struct aml8726_mmc_softc *)arg; + + /* + * The command failed to complete in time so forcefully + * terminate it. + */ + aml8726_mmc_soft_reset(sc, false); + + /* + * Ensure the command has terminated before continuing on + * to things such as bus_dmamap_sync / bus_dmamap_unload. + */ + while ((CSR_READ_4(sc, AML_MMC_IRQ_STATUS_REG) & + AML_MMC_IRQ_STATUS_CMD_BUSY) != 0) + cpu_spinwait(); + + aml8726_mmc_finish_command(sc, MMC_ERR_TIMEOUT); +} + +static void +aml8726_mmc_intr(void *arg) +{ + struct aml8726_mmc_softc *sc = (struct aml8726_mmc_softc *)arg; uint32_t cmdr; - uint32_t icr; uint32_t isr; uint32_t mcfgr; uint32_t previous_byte; uint32_t resp; int mmc_error; - int mmc_stop_error; unsigned int i; AML_MMC_LOCK(sc); @@ -367,12 +466,6 @@ aml8726_mmc_intr(void *arg) if ((cmdr & AML_MMC_CMD_CMD_HAS_DATA) != 0 && (isr & AML_MMC_IRQ_STATUS_WR_CRC16_OK) == 0) mmc_error = MMC_ERR_BADCRC; - } else if ((isr & AML_MMC_IRQ_STATUS_TIMEOUT_IRQ) != 0) { - if (aml8726_mmc_restart_timer(sc) != 0) { - AML_MMC_UNLOCK(sc); - return; - } - mmc_error = MMC_ERR_TIMEOUT; } else { spurious: @@ -389,49 +482,12 @@ spurious: return; } - if ((isr & AML_MMC_IRQ_STATUS_CMD_BUSY) != 0 && - /* - * A multiblock operation may keep the hardware - * busy until stop transmission is executed. - */ - (isr & AML_MMC_IRQ_STATUS_CMD_DONE_IRQ) == 0) { - if (mmc_error == MMC_ERR_NONE) - mmc_error = MMC_ERR_FAILED; - - /* - * Issue a soft reset (while leaving the command complete - * interrupt enabled) to terminate the command. - * - * Ensure the command has terminated before continuing on - * to things such as bus_dmamap_sync / bus_dmamap_unload. - */ - - icr = AML_MMC_IRQ_CONFIG_SOFT_RESET | - AML_MMC_IRQ_CONFIG_CMD_DONE_EN; - - CSR_WRITE_4(sc, AML_MMC_IRQ_CONFIG_REG, icr); - - while ((CSR_READ_4(sc, AML_MMC_IRQ_STATUS_REG) & - AML_MMC_IRQ_STATUS_CMD_BUSY) != 0) - cpu_spinwait(); - } - - /* Clear all interrupts since the request is no longer in flight. */ - CSR_WRITE_4(sc, AML_MMC_IRQ_STATUS_REG, AML_MMC_IRQ_STATUS_CLEAR_IRQ); - CSR_BARRIER(sc, AML_MMC_IRQ_STATUS_REG); - - cmd = sc->cmd; - sc->cmd = NULL; - - cmd->error = mmc_error; - - if ((cmd->flags & MMC_RSP_PRESENT) != 0 && - mmc_error == MMC_ERR_NONE) { + if ((cmdr & AML_MMC_CMD_RESP_BITS_MASK) != 0) { mcfgr = sc->port; mcfgr |= AML_MMC_MULT_CONFIG_RESP_READOUT_EN; CSR_WRITE_4(sc, AML_MMC_MULT_CONFIG_REG, mcfgr); - if ((cmd->flags & MMC_RSP_136) != 0) { + if ((cmdr & AML_MMC_CMD_RESP_CRC7_FROM_8) != 0) { /* * Controller supplies 135:8 instead of @@ -444,48 +500,39 @@ spurious: for (i = 0; i < 4; i++) { resp = CSR_READ_4(sc, AML_MMC_CMD_ARGUMENT_REG); - cmd->resp[3 - i] = (resp << 8) | previous_byte; + sc->cmd->resp[3 - i] = (resp << 8) | + previous_byte; previous_byte = (resp >> 24) & 0xff; } } else - cmd->resp[0] = CSR_READ_4(sc, AML_MMC_CMD_ARGUMENT_REG); + sc->cmd->resp[0] = CSR_READ_4(sc, + AML_MMC_CMD_ARGUMENT_REG); } - data = cmd->data; - - if (data && data->len && - (data->flags & (MMC_DATA_READ | MMC_DATA_WRITE)) != 0) { - if ((data->flags & MMC_DATA_READ) != 0) - bus_dmamap_sync(sc->dmatag, sc->dmamap, - BUS_DMASYNC_POSTREAD); - else - bus_dmamap_sync(sc->dmatag, sc->dmamap, - BUS_DMASYNC_POSTWRITE); - bus_dmamap_unload(sc->dmatag, sc->dmamap); - } + if ((isr & AML_MMC_IRQ_STATUS_CMD_BUSY) != 0 && + /* + * A multiblock operation may keep the hardware + * busy until stop transmission is executed. + */ + (isr & AML_MMC_IRQ_STATUS_CMD_DONE_IRQ) == 0) { + if (mmc_error == MMC_ERR_NONE) + mmc_error = MMC_ERR_FAILED; - /* - * If there's a linked stop command, then start the stop command. - * In order to establish a known state attempt the stop command - * even if the original request encountered an error. - */ + /* + * Issue a soft reset to terminate the command. + * + * Ensure the command has terminated before continuing on + * to things such as bus_dmamap_sync / bus_dmamap_unload. + */ - stop_cmd = (cmd->mrq->stop != cmd) ? cmd->mrq->stop : NULL; + aml8726_mmc_soft_reset(sc, false); - if (stop_cmd != NULL) { - mmc_stop_error = aml8726_mmc_start_command(sc, stop_cmd); - if (mmc_stop_error == MMC_ERR_NONE) { - AML_MMC_UNLOCK(sc); - return; - } - stop_cmd->error = mmc_stop_error; + while ((CSR_READ_4(sc, AML_MMC_IRQ_STATUS_REG) & + AML_MMC_IRQ_STATUS_CMD_BUSY) != 0) + cpu_spinwait(); } - AML_MMC_UNLOCK(sc); - - /* Execute the callback after dropping the lock. */ - if (cmd->mrq) - cmd->mrq->done(cmd->mrq); + aml8726_mmc_finish_command(sc, mmc_error); } static int @@ -698,8 +745,10 @@ aml8726_mmc_attach(device_t dev) goto fail; } - sc->host.f_min = 200000; - sc->host.f_max = 50000000; + callout_init_mtx(&sc->ch, &sc->mtx, CALLOUT_RETURNUNLOCKED); + + sc->host.f_min = aml8726_mmc_freq(sc, aml8726_mmc_div(sc, 200000)); + sc->host.f_max = aml8726_mmc_freq(sc, aml8726_mmc_div(sc, 50000000)); sc->host.host_ocr = sc->voltages[0] | sc->voltages[1]; sc->host.caps = MMC_CAP_4_BIT_DATA | MMC_CAP_HSPEED; @@ -756,10 +805,12 @@ aml8726_mmc_detach(device_t dev) * disable the interrupts, and clear the interrupts. */ (void)aml8726_mmc_power_off(sc); - CSR_WRITE_4(sc, AML_MMC_IRQ_CONFIG_REG, AML_MMC_IRQ_CONFIG_SOFT_RESET); - CSR_BARRIER(sc, AML_MMC_IRQ_CONFIG_REG); + aml8726_mmc_soft_reset(sc, false); CSR_WRITE_4(sc, AML_MMC_IRQ_STATUS_REG, AML_MMC_IRQ_STATUS_CLEAR_IRQ); + /* This should be a NOP since no command was in flight. */ + callout_stop(&sc->ch); + AML_MMC_UNLOCK(sc); bus_generic_detach(dev); @@ -787,8 +838,7 @@ aml8726_mmc_shutdown(device_t dev) * disable the interrupts, and clear the interrupts. */ (void)aml8726_mmc_power_off(sc); - CSR_WRITE_4(sc, AML_MMC_IRQ_CONFIG_REG, AML_MMC_IRQ_CONFIG_SOFT_RESET); - CSR_BARRIER(sc, AML_MMC_IRQ_CONFIG_REG); + aml8726_mmc_soft_reset(sc, false); CSR_WRITE_4(sc, AML_MMC_IRQ_STATUS_REG, AML_MMC_IRQ_STATUS_CLEAR_IRQ); return (0); @@ -799,7 +849,6 @@ aml8726_mmc_update_ios(device_t bus, dev { struct aml8726_mmc_softc *sc = device_get_softc(bus); struct mmc_ios *ios = &sc->host.ios; - unsigned int divisor; int error; int i; uint32_t cfgr; @@ -820,15 +869,8 @@ aml8726_mmc_update_ios(device_t bus, dev return (EINVAL); } - divisor = sc->ref_freq / (ios->clock * 2) - 1; - if (divisor == 0 || divisor == -1) - divisor = 1; - if ((sc->ref_freq / ((divisor + 1) * 2)) > ios->clock) - divisor += 1; - if (divisor > 0x3ff) - divisor = 0x3ff; - - cfgr |= divisor; + cfgr |= aml8726_mmc_div(sc, ios->clock) << + AML_MMC_CONFIG_CMD_CLK_DIV_SHIFT; CSR_WRITE_4(sc, AML_MMC_CONFIG_REG, cfgr); Modified: head/sys/arm/amlogic/aml8726/aml8726_mmc.h ============================================================================== --- head/sys/arm/amlogic/aml8726/aml8726_mmc.h Tue Apr 28 08:20:23 2015 (r282128) +++ head/sys/arm/amlogic/aml8726/aml8726_mmc.h Tue Apr 28 08:27:44 2015 (r282129) @@ -47,20 +47,6 @@ #define AML_MMC_WRITE_TIMEOUT 500 #define AML_MMC_MAX_TIMEOUT 5000 -/* - * Internally the timeout is implemented by counting clock cycles. - * - * Since the hardware implements timeouts by counting cycles - * the minimum read / write timeout (assuming the minimum - * conversion factor of 1 cycle per usec) is: - * - * (8 bits * 512 bytes per block + 16 bits CRC) = 4112 usec - */ -#if ((AML_MMC_READ_TIMEOUT * 1000) < 4112 || \ - (AML_MMC_WRITE_TIMEOUT * 1000) < 4112) -#error "Single block timeout is smaller than supported" -#endif - #define AML_MMC_CMD_ARGUMENT_REG 0 #define AML_MMC_CMD_SEND_REG 4