From owner-svn-src-all@FreeBSD.ORG Thu May 21 17:39:44 2015 Return-Path: Delivered-To: svn-src-all@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 48C12B31; Thu, 21 May 2015 17:39:44 +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 368321D6A; Thu, 21 May 2015 17:39:44 +0000 (UTC) Received: from svn.freebsd.org ([127.0.1.70]) by svn.freebsd.org (8.14.9/8.14.9) with ESMTP id t4LHdicJ027620; Thu, 21 May 2015 17:39:44 GMT (envelope-from loos@FreeBSD.org) Received: (from loos@localhost) by svn.freebsd.org (8.14.9/8.14.9/Submit) id t4LHdhPo027612; Thu, 21 May 2015 17:39:43 GMT (envelope-from loos@FreeBSD.org) Message-Id: <201505211739.t4LHdhPo027612@svn.freebsd.org> X-Authentication-Warning: svn.freebsd.org: loos set sender to loos@FreeBSD.org using -f From: Luiz Otavio O Souza Date: Thu, 21 May 2015 17:39:43 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r283253 - in head/sys: arm/allwinner arm/conf boot/fdt/dts/arm dev/mmc X-SVN-Group: head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-all@freebsd.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: "SVN commit messages for the entire src tree \(except for " user" and " projects" \)" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 21 May 2015 17:39:44 -0000 Author: loos Date: Thu May 21 17:39:42 2015 New Revision: 283253 URL: https://svnweb.freebsd.org/changeset/base/283253 Log: Add the MMC/SD driver for Allwinner SoCs. This is based on the patch sent by Alexander Fedorov with the following fixes/improvements: - Better error handling; - Clock is derived from PLL6 (obtained from netbsd); - No more unnecessary busy loops on interrupt handler; - style(9) fixes and code cleanup. I also want to thanks Martin Galvan who has sent an alternative implementation with some interesting fixes. Tested on CubieBoard2, Banana-Pi (thanks to netgate!) and Cubieboard1 (Pratik Singhal). This is intended to pave the way for the upcoming GSoC work (and make easier the build of images for the supported boards). PR: 196081 Submitted by: Alexander Fedorov Added: head/sys/arm/allwinner/a10_mmc.c (contents, props changed) head/sys/arm/allwinner/a10_mmc.h (contents, props changed) Modified: head/sys/arm/allwinner/a10_clk.c head/sys/arm/allwinner/a10_clk.h head/sys/arm/allwinner/files.allwinner head/sys/arm/conf/CUBIEBOARD head/sys/arm/conf/CUBIEBOARD2 head/sys/boot/fdt/dts/arm/cubieboard.dts head/sys/boot/fdt/dts/arm/cubieboard2.dts head/sys/boot/fdt/dts/arm/sun4i-a10.dtsi head/sys/boot/fdt/dts/arm/sun7i-a20.dtsi head/sys/dev/mmc/mmc.c Modified: head/sys/arm/allwinner/a10_clk.c ============================================================================== --- head/sys/arm/allwinner/a10_clk.c Thu May 21 17:39:42 2015 (r283252) +++ head/sys/arm/allwinner/a10_clk.c Thu May 21 17:39:42 2015 (r283253) @@ -174,7 +174,8 @@ a10_clk_usb_deactivate(void) } int -a10_clk_emac_activate(void) { +a10_clk_emac_activate(void) +{ struct a10_ccm_softc *sc = a10_ccm_sc; uint32_t reg_value; @@ -189,3 +190,110 @@ a10_clk_emac_activate(void) { return (0); } +static void +a10_clk_pll6_enable(void) +{ + struct a10_ccm_softc *sc; + uint32_t reg_value; + + /* + * SATA needs PLL6 to be a 100MHz clock. + * The SATA output frequency is 24MHz * n * k / m / 6. + * To get to 100MHz, k & m must be equal and n must be 25. + * For other uses the output frequency is 24MHz * n * k / 2. + */ + sc = a10_ccm_sc; + reg_value = ccm_read_4(sc, CCM_PLL6_CFG); + reg_value &= ~CCM_PLL_CFG_BYPASS; + reg_value &= ~(CCM_PLL_CFG_FACTOR_K | CCM_PLL_CFG_FACTOR_M | + CCM_PLL_CFG_FACTOR_N); + reg_value |= (25 << CCM_PLL_CFG_FACTOR_N_SHIFT); + reg_value |= CCM_PLL6_CFG_SATA_CLKEN; + reg_value |= CCM_PLL_CFG_ENABLE; + ccm_write_4(sc, CCM_PLL6_CFG, reg_value); +} + +static unsigned int +a10_clk_pll6_get_rate(void) +{ + struct a10_ccm_softc *sc; + uint32_t k, n, reg_value; + + sc = a10_ccm_sc; + reg_value = ccm_read_4(sc, CCM_PLL6_CFG); + n = ((reg_value & CCM_PLL_CFG_FACTOR_N) >> CCM_PLL_CFG_FACTOR_N_SHIFT); + k = ((reg_value & CCM_PLL_CFG_FACTOR_K) >> CCM_PLL_CFG_FACTOR_K_SHIFT) + + 1; + + return ((CCM_CLK_REF_FREQ * n * k) / 2); +} + +int +a10_clk_mmc_activate(int devid) +{ + struct a10_ccm_softc *sc; + uint32_t reg_value; + + sc = a10_ccm_sc; + if (sc == NULL) + return (ENXIO); + + a10_clk_pll6_enable(); + + /* Gating AHB clock for SD/MMC */ + reg_value = ccm_read_4(sc, CCM_AHB_GATING0); + reg_value |= CCM_AHB_GATING_SDMMC0 << devid; + ccm_write_4(sc, CCM_AHB_GATING0, reg_value); + + return (0); +} + +int +a10_clk_mmc_cfg(int devid, int freq) +{ + struct a10_ccm_softc *sc; + uint32_t clksrc, m, n, ophase, phase, reg_value; + unsigned int pll_freq; + + sc = a10_ccm_sc; + if (sc == NULL) + return (ENXIO); + + freq /= 1000; + if (freq <= 400) { + pll_freq = CCM_CLK_REF_FREQ / 1000; + clksrc = CCM_SD_CLK_SRC_SEL_OSC24M; + ophase = 0; + phase = 0; + n = 2; + } else if (freq <= 25000) { + pll_freq = a10_clk_pll6_get_rate() / 1000; + clksrc = CCM_SD_CLK_SRC_SEL_PLL6; + ophase = 0; + phase = 5; + n = 2; + } else if (freq <= 50000) { + pll_freq = a10_clk_pll6_get_rate() / 1000; + clksrc = CCM_SD_CLK_SRC_SEL_PLL6; + ophase = 3; + phase = 5; + n = 0; + } else + return (EINVAL); + m = ((pll_freq / (1 << n)) / (freq)) - 1; + reg_value = ccm_read_4(sc, CCM_MMC0_SCLK_CFG + (devid * 4)); + reg_value &= ~CCM_SD_CLK_SRC_SEL; + reg_value |= (clksrc << CCM_SD_CLK_SRC_SEL_SHIFT); + reg_value &= ~CCM_SD_CLK_PHASE_CTR; + reg_value |= (phase << CCM_SD_CLK_PHASE_CTR_SHIFT); + reg_value &= ~CCM_SD_CLK_DIV_RATIO_N; + reg_value |= (n << CCM_SD_CLK_DIV_RATIO_N_SHIFT); + reg_value &= ~CCM_SD_CLK_OPHASE_CTR; + reg_value |= (ophase << CCM_SD_CLK_OPHASE_CTR_SHIFT); + reg_value &= ~CCM_SD_CLK_DIV_RATIO_M; + reg_value |= m; + reg_value |= CCM_PLL_CFG_ENABLE; + ccm_write_4(sc, CCM_MMC0_SCLK_CFG + (devid * 4), reg_value); + + return (0); +} Modified: head/sys/arm/allwinner/a10_clk.h ============================================================================== --- head/sys/arm/allwinner/a10_clk.h Thu May 21 17:39:42 2015 (r283252) +++ head/sys/arm/allwinner/a10_clk.h Thu May 21 17:39:42 2015 (r283253) @@ -103,6 +103,7 @@ #define CCM_AHB_GATING_USB0 (1 << 0) #define CCM_AHB_GATING_EHCI0 (1 << 1) #define CCM_AHB_GATING_EHCI1 (1 << 3) +#define CCM_AHB_GATING_SDMMC0 (1 << 8) #define CCM_AHB_GATING_EMAC (1 << 17) #define CCM_USB_PHY (1 << 8) @@ -110,8 +111,36 @@ #define CCM_USB1_RESET (1 << 1) #define CCM_USB2_RESET (1 << 2) +#define CCM_PLL_CFG_ENABLE (1U << 31) +#define CCM_PLL_CFG_BYPASS (1U << 30) +#define CCM_PLL_CFG_PLL5 (1U << 25) +#define CCM_PLL_CFG_PLL6 (1U << 24) +#define CCM_PLL_CFG_FACTOR_N 0x1f00 +#define CCM_PLL_CFG_FACTOR_N_SHIFT 8 +#define CCM_PLL_CFG_FACTOR_K 0x30 +#define CCM_PLL_CFG_FACTOR_K_SHIFT 4 +#define CCM_PLL_CFG_FACTOR_M 0x3 + +#define CCM_PLL6_CFG_SATA_CLKEN (1U << 14) + +#define CCM_SD_CLK_SRC_SEL 0x3000000 +#define CCM_SD_CLK_SRC_SEL_SHIFT 24 +#define CCM_SD_CLK_SRC_SEL_OSC24M 0 +#define CCM_SD_CLK_SRC_SEL_PLL6 1 +#define CCM_SD_CLK_PHASE_CTR 0x700000 +#define CCM_SD_CLK_PHASE_CTR_SHIFT 20 +#define CCM_SD_CLK_DIV_RATIO_N 0x30000 +#define CCM_SD_CLK_DIV_RATIO_N_SHIFT 16 +#define CCM_SD_CLK_OPHASE_CTR 0x700 +#define CCM_SD_CLK_OPHASE_CTR_SHIFT 8 +#define CCM_SD_CLK_DIV_RATIO_M 0xf + +#define CCM_CLK_REF_FREQ 24000000U + int a10_clk_usb_activate(void); int a10_clk_usb_deactivate(void); int a10_clk_emac_activate(void); +int a10_clk_mmc_activate(int); +int a10_clk_mmc_cfg(int, int); #endif /* _A10_CLK_H_ */ Added: head/sys/arm/allwinner/a10_mmc.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/sys/arm/allwinner/a10_mmc.c Thu May 21 17:39:42 2015 (r283253) @@ -0,0 +1,689 @@ +/*- + * Copyright (c) 2013 Alexander Fedorov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#define A10_MMC_MEMRES 0 +#define A10_MMC_IRQRES 1 +#define A10_MMC_RESSZ 2 + +struct a10_mmc_softc { + bus_space_handle_t a10_bsh; + bus_space_tag_t a10_bst; + device_t a10_dev; + int a10_bus_busy; + int a10_id; + int a10_resid; + int a10_timeout; + struct callout a10_timeoutc; + struct mmc_host a10_host; + struct mmc_request * a10_req; + struct mtx a10_mtx; + struct resource * a10_res[A10_MMC_RESSZ]; + uint32_t a10_intr; + uint32_t a10_intr_wait; + void * a10_intrhand; +}; + +static struct resource_spec a10_mmc_res_spec[] = { + { SYS_RES_MEMORY, 0, RF_ACTIVE }, + { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE }, + { -1, 0, 0 } +}; + +static int a10_mmc_probe(device_t); +static int a10_mmc_attach(device_t); +static int a10_mmc_detach(device_t); +static int a10_mmc_reset(struct a10_mmc_softc *); +static void a10_mmc_intr(void *); +static int a10_mmc_update_clock(struct a10_mmc_softc *); + +static int a10_mmc_update_ios(device_t, device_t); +static int a10_mmc_request(device_t, device_t, struct mmc_request *); +static int a10_mmc_get_ro(device_t, device_t); +static int a10_mmc_acquire_host(device_t, device_t); +static int a10_mmc_release_host(device_t, device_t); + +#define A10_MMC_LOCK(_sc) mtx_lock(&(_sc)->a10_mtx) +#define A10_MMC_UNLOCK(_sc) mtx_unlock(&(_sc)->a10_mtx) +#define A10_MMC_READ_4(_sc, _reg) \ + bus_space_read_4((_sc)->a10_bst, (_sc)->a10_bsh, _reg) +#define A10_MMC_WRITE_4(_sc, _reg, _value) \ + bus_space_write_4((_sc)->a10_bst, (_sc)->a10_bsh, _reg, _value) + +static int +a10_mmc_probe(device_t dev) +{ + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + if (!ofw_bus_is_compatible(dev, "allwinner,sun4i-a10-mmc")) + return (ENXIO); + device_set_desc(dev, "Allwinner Integrated MMC/SD controller"); + + return (BUS_PROBE_DEFAULT); +} + +static int +a10_mmc_attach(device_t dev) +{ + device_t child; + struct a10_mmc_softc *sc; + struct sysctl_ctx_list *ctx; + struct sysctl_oid_list *tree; + + sc = device_get_softc(dev); + sc->a10_dev = dev; + sc->a10_req = NULL; + sc->a10_id = device_get_unit(dev); + if (sc->a10_id > 3) { + device_printf(dev, "only 4 hosts are supported (0-3)\n"); + return (ENXIO); + } + if (bus_alloc_resources(dev, a10_mmc_res_spec, sc->a10_res) != 0) { + device_printf(dev, "cannot allocate device resources\n"); + return (ENXIO); + } + sc->a10_bst = rman_get_bustag(sc->a10_res[A10_MMC_MEMRES]); + sc->a10_bsh = rman_get_bushandle(sc->a10_res[A10_MMC_MEMRES]); + if (bus_setup_intr(dev, sc->a10_res[A10_MMC_IRQRES], + INTR_TYPE_MISC | INTR_MPSAFE, NULL, a10_mmc_intr, sc, + &sc->a10_intrhand)) { + bus_release_resources(dev, a10_mmc_res_spec, sc->a10_res); + device_printf(dev, "cannot setup interrupt handler\n"); + return (ENXIO); + } + + /* Activate the module clock. */ + if (a10_clk_mmc_activate(sc->a10_id) != 0) { + bus_teardown_intr(dev, sc->a10_res[A10_MMC_IRQRES], + sc->a10_intrhand); + bus_release_resources(dev, a10_mmc_res_spec, sc->a10_res); + device_printf(dev, "cannot activate mmc clock\n"); + return (ENXIO); + } + + sc->a10_timeout = 10; + ctx = device_get_sysctl_ctx(dev); + tree = SYSCTL_CHILDREN(device_get_sysctl_tree(dev)); + SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "req_timeout", CTLFLAG_RW, + &sc->a10_timeout, 0, "Request timeout in seconds"); + mtx_init(&sc->a10_mtx, device_get_nameunit(sc->a10_dev), "a10_mmc", + MTX_DEF); + callout_init_mtx(&sc->a10_timeoutc, &sc->a10_mtx, 0); + + /* Reset controller. */ + if (a10_mmc_reset(sc) != 0) { + device_printf(dev, "cannot reset the controller\n"); + goto fail; + } + + sc->a10_host.f_min = 400000; + sc->a10_host.f_max = 52000000; + sc->a10_host.host_ocr = MMC_OCR_320_330 | MMC_OCR_330_340; + sc->a10_host.caps = MMC_CAP_4_BIT_DATA | MMC_CAP_HSPEED; + sc->a10_host.mode = mode_sd; + + child = device_add_child(dev, "mmc", -1); + if (child == NULL) { + device_printf(dev, "attaching MMC bus failed!\n"); + goto fail; + } + if (device_probe_and_attach(child) != 0) { + device_printf(dev, "attaching MMC child failed!\n"); + device_delete_child(dev, child); + goto fail; + } + + return (0); + +fail: + callout_drain(&sc->a10_timeoutc); + mtx_destroy(&sc->a10_mtx); + bus_teardown_intr(dev, sc->a10_res[A10_MMC_IRQRES], sc->a10_intrhand); + bus_release_resources(dev, a10_mmc_res_spec, sc->a10_res); + + return (ENXIO); +} + +static int +a10_mmc_detach(device_t dev) +{ + + return (EBUSY); +} + +static int +a10_mmc_reset(struct a10_mmc_softc *sc) +{ + int timeout; + + A10_MMC_WRITE_4(sc, A10_MMC_GCTRL, + A10_MMC_READ_4(sc, A10_MMC_GCTRL) | A10_MMC_RESET); + timeout = 1000; + while (--timeout > 0) { + if ((A10_MMC_READ_4(sc, A10_MMC_GCTRL) & A10_MMC_RESET) == 0) + break; + DELAY(100); + } + if (timeout == 0) + return (ETIMEDOUT); + + /* Set the timeout. */ + A10_MMC_WRITE_4(sc, A10_MMC_TIMEOUT, 0xffffffff); + + /* Clear pending interrupts. */ + A10_MMC_WRITE_4(sc, A10_MMC_RINTR, 0xffffffff); + /* Unmask interrupts. */ + A10_MMC_WRITE_4(sc, A10_MMC_IMASK, + A10_MMC_CMD_DONE | A10_MMC_INT_ERR_BIT | + A10_MMC_DATA_OVER | A10_MMC_AUTOCMD_DONE | + A10_MMC_RX_DATA_REQ | A10_MMC_TX_DATA_REQ); + /* Enable interrupts and AHB access. */ + A10_MMC_WRITE_4(sc, A10_MMC_GCTRL, + A10_MMC_READ_4(sc, A10_MMC_GCTRL) | + A10_MMC_INT_ENABLE | A10_MMC_ACCESS_BY_AHB); + + return (0); +} + +static void +a10_mmc_req_done(struct a10_mmc_softc *sc) +{ + struct mmc_command *cmd; + struct mmc_request *req; + + cmd = sc->a10_req->cmd; + if (cmd->error != MMC_ERR_NONE) { + /* Reset the controller. */ + a10_mmc_reset(sc); + a10_mmc_update_clock(sc); + } + /* Reset the FIFO. */ + A10_MMC_WRITE_4(sc, A10_MMC_GCTRL, + A10_MMC_READ_4(sc, A10_MMC_GCTRL) | A10_MMC_FIFO_RESET); + + req = sc->a10_req; + callout_stop(&sc->a10_timeoutc); + sc->a10_req = NULL; + sc->a10_intr = 0; + sc->a10_resid = 0; + sc->a10_intr_wait = 0; + req->done(req); +} + +static void +a10_mmc_req_ok(struct a10_mmc_softc *sc) +{ + int timeout; + struct mmc_command *cmd; + uint32_t status; + + timeout = 1000; + while (--timeout > 0) { + status = A10_MMC_READ_4(sc, A10_MMC_STAS); + if ((status & A10_MMC_CARD_DATA_BUSY) == 0) + break; + DELAY(1000); + } + cmd = sc->a10_req->cmd; + if (timeout == 0) { + cmd->error = MMC_ERR_FAILED; + a10_mmc_req_done(sc); + return; + } + if (cmd->flags & MMC_RSP_PRESENT) { + if (cmd->flags & MMC_RSP_136) { + cmd->resp[0] = A10_MMC_READ_4(sc, A10_MMC_RESP3); + cmd->resp[1] = A10_MMC_READ_4(sc, A10_MMC_RESP2); + cmd->resp[2] = A10_MMC_READ_4(sc, A10_MMC_RESP1); + cmd->resp[3] = A10_MMC_READ_4(sc, A10_MMC_RESP0); + } else + cmd->resp[0] = A10_MMC_READ_4(sc, A10_MMC_RESP0); + } + /* All data has been transferred ? */ + if (cmd->data != NULL && (sc->a10_resid << 2) < cmd->data->len) + cmd->error = MMC_ERR_FAILED; + a10_mmc_req_done(sc); +} + +static void +a10_mmc_timeout(void *arg) +{ + struct a10_mmc_softc *sc; + + sc = (struct a10_mmc_softc *)arg; + if (sc->a10_req != NULL) { + device_printf(sc->a10_dev, "controller timeout\n"); + sc->a10_req->cmd->error = MMC_ERR_TIMEOUT; + a10_mmc_req_done(sc); + } else + device_printf(sc->a10_dev, + "Spurious timeout - no active request\n"); +} + +static int +a10_mmc_pio_transfer(struct a10_mmc_softc *sc, struct mmc_data *data) +{ + int i, write; + uint32_t bit, *buf; + + buf = (uint32_t *)data->data; + write = (data->flags & MMC_DATA_WRITE) ? 1 : 0; + bit = write ? A10_MMC_FIFO_FULL : A10_MMC_FIFO_EMPTY; + for (i = sc->a10_resid; i < (data->len >> 2); i++) { + if ((A10_MMC_READ_4(sc, A10_MMC_STAS) & bit)) + return (1); + if (write) + A10_MMC_WRITE_4(sc, A10_MMC_FIFO, buf[i]); + else + buf[i] = A10_MMC_READ_4(sc, A10_MMC_FIFO); + sc->a10_resid = i + 1; + } + + return (0); +} + +static void +a10_mmc_intr(void *arg) +{ + struct a10_mmc_softc *sc; + struct mmc_data *data; + uint32_t imask, rint; + + sc = (struct a10_mmc_softc *)arg; + A10_MMC_LOCK(sc); + rint = A10_MMC_READ_4(sc, A10_MMC_RINTR); + imask = A10_MMC_READ_4(sc, A10_MMC_IMASK); + if (imask == 0 && rint == 0) { + A10_MMC_UNLOCK(sc); + return; + } +#ifdef DEBUG + device_printf(sc->a10_dev, "imask: %#x, rint: %#x\n", imask, rint); +#endif + if (sc->a10_req == NULL) { + device_printf(sc->a10_dev, + "Spurious interrupt - no active request, rint: 0x%08X\n", + rint); + A10_MMC_WRITE_4(sc, A10_MMC_RINTR, rint); + A10_MMC_UNLOCK(sc); + return; + } + if (rint & A10_MMC_INT_ERR_BIT) { + device_printf(sc->a10_dev, "error rint: 0x%08X\n", rint); + if (rint & A10_MMC_RESP_TIMEOUT) + sc->a10_req->cmd->error = MMC_ERR_TIMEOUT; + else + sc->a10_req->cmd->error = MMC_ERR_FAILED; + A10_MMC_WRITE_4(sc, A10_MMC_RINTR, rint); + a10_mmc_req_done(sc); + A10_MMC_UNLOCK(sc); + return; + } + + sc->a10_intr |= rint; + data = sc->a10_req->cmd->data; + if (data != NULL && (rint & (A10_MMC_DATA_OVER | + A10_MMC_RX_DATA_REQ | A10_MMC_TX_DATA_REQ)) != 0) + a10_mmc_pio_transfer(sc, data); + if ((sc->a10_intr & sc->a10_intr_wait) == sc->a10_intr_wait) + a10_mmc_req_ok(sc); + + A10_MMC_WRITE_4(sc, A10_MMC_RINTR, rint); + A10_MMC_UNLOCK(sc); +} + +static int +a10_mmc_request(device_t bus, device_t child, struct mmc_request *req) +{ + int blksz; + struct a10_mmc_softc *sc; + struct mmc_command *cmd; + uint32_t cmdreg; + + sc = device_get_softc(bus); + A10_MMC_LOCK(sc); + if (sc->a10_req) { + A10_MMC_UNLOCK(sc); + return (EBUSY); + } + sc->a10_req = req; + cmd = req->cmd; + cmdreg = A10_MMC_START; + if (cmd->opcode == MMC_GO_IDLE_STATE) + cmdreg |= A10_MMC_SEND_INIT_SEQ; + if (cmd->flags & MMC_RSP_PRESENT) + cmdreg |= A10_MMC_RESP_EXP; + if (cmd->flags & MMC_RSP_136) + cmdreg |= A10_MMC_LONG_RESP; + if (cmd->flags & MMC_RSP_CRC) + cmdreg |= A10_MMC_CHECK_RESP_CRC; + + sc->a10_intr = 0; + sc->a10_resid = 0; + sc->a10_intr_wait = A10_MMC_CMD_DONE; + cmd->error = MMC_ERR_NONE; + if (cmd->data != NULL) { + sc->a10_intr_wait |= A10_MMC_DATA_OVER; + cmdreg |= A10_MMC_DATA_EXP | A10_MMC_WAIT_PREOVER; + if (cmd->data->flags & MMC_DATA_MULTI) { + cmdreg |= A10_MMC_SEND_AUTOSTOP; + sc->a10_intr_wait |= A10_MMC_AUTOCMD_DONE; + } + if (cmd->data->flags & MMC_DATA_WRITE) + cmdreg |= A10_MMC_WRITE; + blksz = min(cmd->data->len, MMC_SECTOR_SIZE); + A10_MMC_WRITE_4(sc, A10_MMC_BLKSZ, blksz); + A10_MMC_WRITE_4(sc, A10_MMC_BCNTR, cmd->data->len); + } + + A10_MMC_WRITE_4(sc, A10_MMC_CARG, cmd->arg); + A10_MMC_WRITE_4(sc, A10_MMC_CMDR, cmdreg | cmd->opcode); + callout_reset(&sc->a10_timeoutc, sc->a10_timeout * hz, + a10_mmc_timeout, sc); + A10_MMC_UNLOCK(sc); + + return (0); +} + +static int +a10_mmc_read_ivar(device_t bus, device_t child, int which, + uintptr_t *result) +{ + struct a10_mmc_softc *sc; + + sc = device_get_softc(bus); + switch (which) { + default: + return (EINVAL); + case MMCBR_IVAR_BUS_MODE: + *(int *)result = sc->a10_host.ios.bus_mode; + break; + case MMCBR_IVAR_BUS_WIDTH: + *(int *)result = sc->a10_host.ios.bus_width; + break; + case MMCBR_IVAR_CHIP_SELECT: + *(int *)result = sc->a10_host.ios.chip_select; + break; + case MMCBR_IVAR_CLOCK: + *(int *)result = sc->a10_host.ios.clock; + break; + case MMCBR_IVAR_F_MIN: + *(int *)result = sc->a10_host.f_min; + break; + case MMCBR_IVAR_F_MAX: + *(int *)result = sc->a10_host.f_max; + break; + case MMCBR_IVAR_HOST_OCR: + *(int *)result = sc->a10_host.host_ocr; + break; + case MMCBR_IVAR_MODE: + *(int *)result = sc->a10_host.mode; + break; + case MMCBR_IVAR_OCR: + *(int *)result = sc->a10_host.ocr; + break; + case MMCBR_IVAR_POWER_MODE: + *(int *)result = sc->a10_host.ios.power_mode; + break; + case MMCBR_IVAR_VDD: + *(int *)result = sc->a10_host.ios.vdd; + break; + case MMCBR_IVAR_CAPS: + *(int *)result = sc->a10_host.caps; + break; + case MMCBR_IVAR_MAX_DATA: + *(int *)result = 65535; + break; + } + + return (0); +} + +static int +a10_mmc_write_ivar(device_t bus, device_t child, int which, + uintptr_t value) +{ + struct a10_mmc_softc *sc; + + sc = device_get_softc(bus); + switch (which) { + default: + return (EINVAL); + case MMCBR_IVAR_BUS_MODE: + sc->a10_host.ios.bus_mode = value; + break; + case MMCBR_IVAR_BUS_WIDTH: + sc->a10_host.ios.bus_width = value; + break; + case MMCBR_IVAR_CHIP_SELECT: + sc->a10_host.ios.chip_select = value; + break; + case MMCBR_IVAR_CLOCK: + sc->a10_host.ios.clock = value; + break; + case MMCBR_IVAR_MODE: + sc->a10_host.mode = value; + break; + case MMCBR_IVAR_OCR: + sc->a10_host.ocr = value; + break; + case MMCBR_IVAR_POWER_MODE: + sc->a10_host.ios.power_mode = value; + break; + case MMCBR_IVAR_VDD: + sc->a10_host.ios.vdd = value; + break; + /* These are read-only */ + case MMCBR_IVAR_CAPS: + case MMCBR_IVAR_HOST_OCR: + case MMCBR_IVAR_F_MIN: + case MMCBR_IVAR_F_MAX: + case MMCBR_IVAR_MAX_DATA: + return (EINVAL); + } + + return (0); +} + +static int +a10_mmc_update_clock(struct a10_mmc_softc *sc) +{ + uint32_t cmdreg; + int retry; + + cmdreg = A10_MMC_START | A10_MMC_UPCLK_ONLY | + A10_MMC_WAIT_PREOVER; + A10_MMC_WRITE_4(sc, A10_MMC_CMDR, cmdreg); + retry = 0xfffff; + while (--retry > 0) { + if ((A10_MMC_READ_4(sc, A10_MMC_CMDR) & A10_MMC_START) == 0) { + A10_MMC_WRITE_4(sc, A10_MMC_RINTR, 0xffffffff); + return (0); + } + DELAY(10); + } + A10_MMC_WRITE_4(sc, A10_MMC_RINTR, 0xffffffff); + device_printf(sc->a10_dev, "timeout updating clock\n"); + + return (ETIMEDOUT); +} + +static int +a10_mmc_update_ios(device_t bus, device_t child) +{ + int error; + struct a10_mmc_softc *sc; + struct mmc_ios *ios; + uint32_t clkcr; + + sc = device_get_softc(bus); + clkcr = A10_MMC_READ_4(sc, A10_MMC_CLKCR); + if (clkcr & A10_MMC_CARD_CLK_ON) { + /* Disable clock. */ + clkcr &= ~A10_MMC_CARD_CLK_ON; + A10_MMC_WRITE_4(sc, A10_MMC_CLKCR, clkcr); + error = a10_mmc_update_clock(sc); + if (error != 0) + return (error); + } + + ios = &sc->a10_host.ios; + if (ios->clock) { + /* Reset the divider. */ + clkcr &= ~A10_MMC_CLKCR_DIV; + A10_MMC_WRITE_4(sc, A10_MMC_CLKCR, clkcr); + error = a10_mmc_update_clock(sc); + if (error != 0) + return (error); + + /* Set the MMC clock. */ + error = a10_clk_mmc_cfg(sc->a10_id, ios->clock); + if (error != 0) + return (error); + + /* Enable clock. */ + clkcr |= A10_MMC_CARD_CLK_ON; + A10_MMC_WRITE_4(sc, A10_MMC_CLKCR, clkcr); + error = a10_mmc_update_clock(sc); + if (error != 0) + return (error); + } + + /* Set the bus width. */ + switch (ios->bus_width) { + case bus_width_1: + A10_MMC_WRITE_4(sc, A10_MMC_WIDTH, A10_MMC_WIDTH1); + break; + case bus_width_4: + A10_MMC_WRITE_4(sc, A10_MMC_WIDTH, A10_MMC_WIDTH4); + break; + case bus_width_8: + A10_MMC_WRITE_4(sc, A10_MMC_WIDTH, A10_MMC_WIDTH8); + break; + } + + return (0); +} + +static int +a10_mmc_get_ro(device_t bus, device_t child) +{ + + return (0); +} + +static int +a10_mmc_acquire_host(device_t bus, device_t child) +{ + struct a10_mmc_softc *sc; + int error; + + sc = device_get_softc(bus); + A10_MMC_LOCK(sc); + while (sc->a10_bus_busy) { + error = msleep(sc, &sc->a10_mtx, PCATCH, "mmchw", 0); + if (error != 0) { + A10_MMC_UNLOCK(sc); + return (error); + } + } + sc->a10_bus_busy++; + A10_MMC_UNLOCK(sc); + + return (0); +} + +static int +a10_mmc_release_host(device_t bus, device_t child) +{ + struct a10_mmc_softc *sc; + + sc = device_get_softc(bus); + A10_MMC_LOCK(sc); + sc->a10_bus_busy--; + wakeup(sc); + A10_MMC_UNLOCK(sc); + + return (0); +} + +static device_method_t a10_mmc_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, a10_mmc_probe), + DEVMETHOD(device_attach, a10_mmc_attach), + DEVMETHOD(device_detach, a10_mmc_detach), + + /* Bus interface */ + DEVMETHOD(bus_read_ivar, a10_mmc_read_ivar), + DEVMETHOD(bus_write_ivar, a10_mmc_write_ivar), + DEVMETHOD(bus_print_child, bus_generic_print_child), + + /* MMC bridge interface */ + DEVMETHOD(mmcbr_update_ios, a10_mmc_update_ios), + DEVMETHOD(mmcbr_request, a10_mmc_request), + DEVMETHOD(mmcbr_get_ro, a10_mmc_get_ro), + DEVMETHOD(mmcbr_acquire_host, a10_mmc_acquire_host), + DEVMETHOD(mmcbr_release_host, a10_mmc_release_host), + + DEVMETHOD_END +}; + +static devclass_t a10_mmc_devclass; + +static driver_t a10_mmc_driver = { + "a10_mmc", + a10_mmc_methods, + sizeof(struct a10_mmc_softc), +}; + +DRIVER_MODULE(a10_mmc, simplebus, a10_mmc_driver, a10_mmc_devclass, 0, 0); Added: head/sys/arm/allwinner/a10_mmc.h ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/sys/arm/allwinner/a10_mmc.h Thu May 21 17:39:42 2015 (r283253) @@ -0,0 +1,179 @@ +/*- + * Copyright (c) 2013 Alexander Fedorov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _A10_MMC_H_ +#define _A10_MMC_H_ + +#define A10_MMC_GCTRL 0x00 /* Global Control Register */ +#define A10_MMC_CLKCR 0x04 /* Clock Control Register */ +#define A10_MMC_TIMEOUT 0x08 /* Timeout Register */ +#define A10_MMC_WIDTH 0x0C /* Bus Width Register */ +#define A10_MMC_BLKSZ 0x10 /* Block Size Register */ +#define A10_MMC_BCNTR 0x14 /* Byte Count Register */ +#define A10_MMC_CMDR 0x18 /* Command Register */ +#define A10_MMC_CARG 0x1C /* Argument Register */ +#define A10_MMC_RESP0 0x20 /* Response Register 0 */ +#define A10_MMC_RESP1 0x24 /* Response Register 1 */ +#define A10_MMC_RESP2 0x28 /* Response Register 2 */ +#define A10_MMC_RESP3 0x2C /* Response Register 3 */ +#define A10_MMC_IMASK 0x30 /* Interrupt Mask Register */ +#define A10_MMC_MISTA 0x34 /* Masked Interrupt Status Register */ +#define A10_MMC_RINTR 0x38 /* Raw Interrupt Status Register */ +#define A10_MMC_STAS 0x3C /* Status Register */ +#define A10_MMC_FTRGL 0x40 /* FIFO Threshold Watermark Register */ +#define A10_MMC_FUNS 0x44 /* Function Select Register */ +#define A10_MMC_CBCR 0x48 /* CIU Byte Count Register */ +#define A10_MMC_BBCR 0x4C /* BIU Byte Count Register */ +#define A10_MMC_DBGC 0x50 /* Debug Enable Register */ +#define A10_MMC_DMAC 0x80 /* IDMAC Control Register */ +#define A10_MMC_DLBA 0x84 /* IDMAC Desc List Base Address Reg */ +#define A10_MMC_IDST 0x88 /* IDMAC Status Register */ +#define A10_MMC_IDIE 0x8C /* IDMAC Interrupt Enable Register */ +#define A10_MMC_CHDA 0x90 +#define A10_MMC_CBDA 0x94 +#define A10_MMC_FIFO 0x100 /* FIFO Access Address */ + +/* A10_MMC_GCTRL */ +#define A10_MMC_SOFT_RESET (1U << 0) +#define A10_MMC_FIFO_RESET (1U << 1) +#define A10_MMC_DMA_RESET (1U << 2) +#define A10_MMC_INT_ENABLE (1U << 4) +#define A10_MMC_DMA_ENABLE (1U << 5) +#define A10_MMC_DEBOUNCE_ENABLE (1U << 8) +#define A10_MMC_DDR_MODE (1U << 10) +#define A10_MMC_ACCESS_BY_DMA (1U << 30) +#define A10_MMC_ACCESS_BY_AHB (1U << 31) +#define A10_MMC_RESET \ + (A10_MMC_SOFT_RESET | A10_MMC_FIFO_RESET | A10_MMC_DMA_RESET) + +/* A10_MMC_CLKCR */ +#define A10_MMC_CARD_CLK_ON (1U << 16) +#define A10_MMC_LOW_POWER_ON (1U << 17) +#define A10_MMC_CLKCR_DIV 0xff + +/* A10_MMC_WIDTH */ +#define A10_MMC_WIDTH1 0 +#define A10_MMC_WIDTH4 1 +#define A10_MMC_WIDTH8 2 + +/* A10_MMC_CMDR */ +#define A10_MMC_RESP_EXP (1U << 6) +#define A10_MMC_LONG_RESP (1U << 7) +#define A10_MMC_CHECK_RESP_CRC (1U << 8) +#define A10_MMC_DATA_EXP (1U << 9) +#define A10_MMC_WRITE (1U << 10) +#define A10_MMC_SEQ_MODE (1U << 11) +#define A10_MMC_SEND_AUTOSTOP (1U << 12) +#define A10_MMC_WAIT_PREOVER (1U << 13) +#define A10_MMC_STOP_ABORT_CMD (1U << 14) +#define A10_MMC_SEND_INIT_SEQ (1U << 15) +#define A10_MMC_UPCLK_ONLY (1U << 21) +#define A10_MMC_RDCEATADEV (1U << 22) +#define A10_MMC_CCS_EXP (1U << 23) +#define A10_MMC_ENB_BOOT (1U << 24) +#define A10_MMC_ALT_BOOT_OPT (1U << 25) +#define A10_MMC_BOOT_ACK_EXP (1U << 26) +#define A10_MMC_DISABLE_BOOT (1U << 27) +#define A10_MMC_VOL_SWITCH (1U << 28) +#define A10_MMC_START (1U << 31) + +/* A10_MMC_IMASK and A10_MMC_RINTR */ +#define A10_MMC_RESP_ERR (1U << 1) +#define A10_MMC_CMD_DONE (1U << 2) +#define A10_MMC_DATA_OVER (1U << 3) +#define A10_MMC_TX_DATA_REQ (1U << 4) +#define A10_MMC_RX_DATA_REQ (1U << 5) +#define A10_MMC_RESP_CRC_ERR (1U << 6) +#define A10_MMC_DATA_CRC_ERR (1U << 7) +#define A10_MMC_RESP_TIMEOUT (1U << 8) +#define A10_MMC_ACK_RECV (1U << 8) +#define A10_MMC_DATA_TIMEOUT (1U << 9) +#define A10_MMC_BOOT_START (1U << 9) +#define A10_MMC_DATA_STARVE (1U << 10) +#define A10_MMC_VOL_CHG_DONE (1U << 10) +#define A10_MMC_FIFO_RUN_ERR (1U << 11) +#define A10_MMC_HARDW_LOCKED (1U << 12) +#define A10_MMC_START_BIT_ERR (1U << 13) +#define A10_MMC_AUTOCMD_DONE (1U << 14) +#define A10_MMC_END_BIT_ERR (1U << 15) +#define A10_MMC_SDIO_INT (1U << 16) +#define A10_MMC_CARD_INSERT (1U << 30) +#define A10_MMC_CARD_REMOVE (1U << 31) +#define A10_MMC_INT_ERR_BIT \ + (A10_MMC_RESP_ERR | A10_MMC_RESP_CRC_ERR | \ + A10_MMC_DATA_CRC_ERR | A10_MMC_RESP_TIMEOUT | \ + A10_MMC_FIFO_RUN_ERR | A10_MMC_HARDW_LOCKED | \ + A10_MMC_START_BIT_ERR | A10_MMC_END_BIT_ERR) + +/* A10_MMC_STAS */ +#define A10_MMC_RX_WLFLAG (1U << 0) +#define A10_MMC_TX_WLFLAG (1U << 1) *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***