Index: head/sys/arm/amlogic/aml8726/aml8726_ccm.c =================================================================== --- head/sys/arm/amlogic/aml8726/aml8726_ccm.c (revision 283182) +++ head/sys/arm/amlogic/aml8726/aml8726_ccm.c (revision 283183) @@ -1,231 +1,230 @@ /*- * Copyright 2015 John Wehle * 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. */ /* * Amlogic aml8726 clock control module driver. * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct aml8726_ccm_softc { device_t dev; struct aml8726_ccm_function *soc; struct resource *res[1]; struct mtx mtx; }; static struct resource_spec aml8726_ccm_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { -1, 0 } }; #define AML_CCM_LOCK(sc) mtx_lock(&(sc)->mtx) #define AML_CCM_UNLOCK(sc) mtx_unlock(&(sc)->mtx) #define AML_CCM_LOCK_INIT(sc) \ mtx_init(&(sc)->mtx, device_get_nameunit((sc)->dev), \ "ccm", MTX_DEF) #define AML_CCM_LOCK_DESTROY(sc) mtx_destroy(&(sc)->mtx); #define CSR_WRITE_4(sc, reg, val) bus_write_4((sc)->res[0], reg, (val)) #define CSR_READ_4(sc, reg) bus_read_4((sc)->res[0], reg) static int aml8726_ccm_configure_gates(struct aml8726_ccm_softc *sc) { struct aml8726_ccm_function *f; struct aml8726_ccm_gate *g; char *function_name; char *functions; phandle_t node; ssize_t len; uint32_t value; node = ofw_bus_get_node(sc->dev); len = OF_getprop_alloc(node, "functions", sizeof(char), (void **)&functions); if (len < 0) { device_printf(sc->dev, "missing functions attribute in FDT\n"); return (ENXIO); } function_name = functions; while (len) { for (f = sc->soc; f->name != NULL; f++) if (strncmp(f->name, function_name, len) == 0) break; if (f->name == NULL) { /* display message prior to queuing up next string */ device_printf(sc->dev, "unknown function attribute %.*s in FDT\n", len, function_name); } /* queue up next string */ while (*function_name && len) { function_name++; len--; } if (len) { function_name++; len--; } if (f->name == NULL) continue; AML_CCM_LOCK(sc); /* * Enable the clock gates necessary for the function. * * In some cases a clock may be shared across functions * (meaning don't disable a clock without ensuring that * it's not required by someone else). */ for (g = f->gates; g->bits != 0x00000000; g++) { value = CSR_READ_4(sc, g->addr); value |= g->bits; CSR_WRITE_4(sc, g->addr, value); } AML_CCM_UNLOCK(sc); } free(functions, M_OFWPROP); return (0); } static int aml8726_ccm_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "amlogic,aml8726-ccm")) return (ENXIO); device_set_desc(dev, "Amlogic aml8726 ccm"); return (BUS_PROBE_DEFAULT); } static int aml8726_ccm_attach(device_t dev) { struct aml8726_ccm_softc *sc = device_get_softc(dev); sc->dev = dev; switch (aml8726_soc_hw_rev) { case AML_SOC_HW_REV_M3: sc->soc = aml8726_m3_ccm; break; case AML_SOC_HW_REV_M6: sc->soc = aml8726_m6_ccm; break; case AML_SOC_HW_REV_M8: sc->soc = aml8726_m8_ccm; break; case AML_SOC_HW_REV_M8B: sc->soc = aml8726_m8b_ccm; break; default: device_printf(dev, "unsupported SoC\n"); return (ENXIO); /* NOTREACHED */ - break; } if (bus_alloc_resources(dev, aml8726_ccm_spec, sc->res)) { device_printf(dev, "can not allocate resources for device\n"); return (ENXIO); } AML_CCM_LOCK_INIT(sc); return (aml8726_ccm_configure_gates(sc)); } static int aml8726_ccm_detach(device_t dev) { struct aml8726_ccm_softc *sc = device_get_softc(dev); AML_CCM_LOCK_DESTROY(sc); bus_release_resources(dev, aml8726_ccm_spec, sc->res); return (0); } static device_method_t aml8726_ccm_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aml8726_ccm_probe), DEVMETHOD(device_attach, aml8726_ccm_attach), DEVMETHOD(device_detach, aml8726_ccm_detach), DEVMETHOD_END }; static driver_t aml8726_ccm_driver = { "ccm", aml8726_ccm_methods, sizeof(struct aml8726_ccm_softc), }; static devclass_t aml8726_ccm_devclass; EARLY_DRIVER_MODULE(ccm, simplebus, aml8726_ccm_driver, aml8726_ccm_devclass, 0, 0, BUS_PASS_CPU + BUS_PASS_ORDER_LATE); Index: head/sys/arm/amlogic/aml8726/aml8726_pinctrl.c =================================================================== --- head/sys/arm/amlogic/aml8726/aml8726_pinctrl.c (revision 283182) +++ head/sys/arm/amlogic/aml8726/aml8726_pinctrl.c (revision 283183) @@ -1,434 +1,433 @@ /*- * Copyright 2015 John Wehle * 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. */ /* * Amlogic aml8726 pinctrl driver. * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct aml8726_pinctrl_softc { device_t dev; struct { struct aml8726_pinctrl_function *func; struct aml8726_pinctrl_pkg_pin *ppin; boolean_t pud_ctrl; } soc; struct resource *res[6]; struct mtx mtx; }; static struct resource_spec aml8726_pinctrl_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, /* mux */ { SYS_RES_MEMORY, 1, RF_ACTIVE | RF_SHAREABLE }, /* pu/pd */ { SYS_RES_MEMORY, 2, RF_ACTIVE | RF_SHAREABLE }, /* pull enable */ { SYS_RES_MEMORY, 3, RF_ACTIVE }, /* ao mux */ { SYS_RES_MEMORY, 4, RF_ACTIVE | RF_SHAREABLE }, /* ao pu/pd */ { SYS_RES_MEMORY, 5, RF_ACTIVE | RF_SHAREABLE }, /* ao pull enable */ { -1, 0 } }; #define AML_PINCTRL_LOCK(sc) mtx_lock(&(sc)->mtx) #define AML_PINCTRL_UNLOCK(sc) mtx_unlock(&(sc)->mtx) #define AML_PINCTRL_LOCK_INIT(sc) \ mtx_init(&(sc)->mtx, device_get_nameunit((sc)->dev), \ "pinctrl", MTX_DEF) #define AML_PINCTRL_LOCK_DESTROY(sc) mtx_destroy(&(sc)->mtx); #define MUX_WRITE_4(sc, reg, val) bus_write_4((sc)->res[0], reg, (val)) #define MUX_READ_4(sc, reg) bus_read_4((sc)->res[0], reg) #define PUD_WRITE_4(sc, reg, val) bus_write_4((sc)->res[1], reg, (val)) #define PUD_READ_4(sc, reg) bus_read_4((sc)->res[1], reg) #define PEN_WRITE_4(sc, reg, val) bus_write_4((sc)->res[2], reg, (val)) #define PEN_READ_4(sc, reg) bus_read_4((sc)->res[2], reg) #define AOMUX_WRITE_4(sc, reg, val) bus_write_4((sc)->res[3], reg, (val)) #define AOMUX_READ_4(sc, reg) bus_read_4((sc)->res[3], reg) #define AOPUD_WRITE_4(sc, reg, val) bus_write_4((sc)->res[4], reg, (val)) #define AOPUD_READ_4(sc, reg) bus_read_4((sc)->res[4], reg) #define AOPEN_WRITE_4(sc, reg, val) bus_write_4((sc)->res[5], reg, (val)) #define AOPEN_READ_4(sc, reg) bus_read_4((sc)->res[5], reg) static int aml8726_pinctrl_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "amlogic,aml8726-pinctrl")) return (ENXIO); device_set_desc(dev, "Amlogic aml8726 pinctrl"); return (BUS_PROBE_DEFAULT); } static int aml8726_pinctrl_attach(device_t dev) { struct aml8726_pinctrl_softc *sc = device_get_softc(dev); sc->dev = dev; sc->soc.pud_ctrl = false; switch (aml8726_soc_hw_rev) { case AML_SOC_HW_REV_M3: sc->soc.func = aml8726_m3_pinctrl; sc->soc.ppin = aml8726_m3_pkg_pin; break; case AML_SOC_HW_REV_M6: sc->soc.func = aml8726_m6_pinctrl; sc->soc.ppin = aml8726_m6_pkg_pin; break; case AML_SOC_HW_REV_M8: sc->soc.func = aml8726_m8_pinctrl; sc->soc.ppin = aml8726_m8_pkg_pin; sc->soc.pud_ctrl = true; break; case AML_SOC_HW_REV_M8B: sc->soc.func = aml8726_m8b_pinctrl; sc->soc.ppin = aml8726_m8b_pkg_pin; sc->soc.pud_ctrl = true; break; default: device_printf(dev, "unsupported SoC\n"); return (ENXIO); /* NOTREACHED */ - break; } if (bus_alloc_resources(dev, aml8726_pinctrl_spec, sc->res)) { device_printf(dev, "could not allocate resources for device\n"); return (ENXIO); } AML_PINCTRL_LOCK_INIT(sc); fdt_pinctrl_register(dev, "amlogic,pins"); fdt_pinctrl_configure_tree(dev); return (0); } static int aml8726_pinctrl_detach(device_t dev) { struct aml8726_pinctrl_softc *sc = device_get_softc(dev); AML_PINCTRL_LOCK_DESTROY(sc); bus_release_resources(dev, aml8726_pinctrl_spec, sc->res); return (0); } static int aml8726_pinctrl_configure_pins(device_t dev, phandle_t cfgxref) { struct aml8726_pinctrl_softc *sc = device_get_softc(dev); struct aml8726_pinctrl_function *cf; struct aml8726_pinctrl_function *f; struct aml8726_pinctrl_pkg_pin *pp; struct aml8726_pinctrl_pin *cp; struct aml8726_pinctrl_pin *p; enum aml8726_pinctrl_pull_mode pm; char *function_name; char *pins; char *pin_name; char *pull; phandle_t node; ssize_t len; uint32_t value; node = OF_node_from_xref(cfgxref); len = OF_getprop_alloc(node, "amlogic,function", sizeof(char), (void **)&function_name); if (len < 0) { device_printf(dev, "missing amlogic,function attribute in FDT\n"); return (ENXIO); } for (f = sc->soc.func; f->name != NULL; f++) if (strncmp(f->name, function_name, len) == 0) break; if (f->name == NULL) { device_printf(dev, "unknown function attribute %.*s in FDT\n", len, function_name); free(function_name, M_OFWPROP); return (ENXIO); } free(function_name, M_OFWPROP); len = OF_getprop_alloc(node, "amlogic,pull", sizeof(char), (void **)&pull); pm = aml8726_unknown_pm; if (len > 0) { if (strncmp(pull, "enable", len) == 0) pm = aml8726_enable_pm; else if (strncmp(pull, "disable", len) == 0) pm = aml8726_disable_pm; else if (strncmp(pull, "down", len) == 0) pm = aml8726_enable_down_pm; else if (strncmp(pull, "up", len) == 0) pm = aml8726_enable_up_pm; else { device_printf(dev, "unknown pull attribute %.*s in FDT\n", len, pull); free(pull, M_OFWPROP); return (ENXIO); } } free(pull, M_OFWPROP); /* * Setting the pull direction isn't supported on all SoC. */ switch (pm) { case aml8726_enable_down_pm: case aml8726_enable_up_pm: if (sc->soc.pud_ctrl == false) { device_printf(dev, "SoC doesn't support setting pull direction.\n"); return (ENXIO); } break; default: break; } len = OF_getprop_alloc(node, "amlogic,pins", sizeof(char), (void **)&pins); if (len < 0) { device_printf(dev, "missing amlogic,pins attribute in FDT\n"); return (ENXIO); } pin_name = pins; while (len) { for (p = f->pins; p->name != NULL; p++) if (strncmp(p->name, pin_name, len) == 0) break; if (p->name == NULL) { /* display message prior to queuing up next string */ device_printf(dev, "unknown pin attribute %.*s in FDT\n", len, pin_name); } /* queue up next string */ while (*pin_name && len) { pin_name++; len--; } if (len) { pin_name++; len--; } if (p->name == NULL) continue; for (pp = sc->soc.ppin; pp->pkg_name != NULL; pp++) if (strcmp(pp->pkg_name, p->pkg_name) == 0) break; if (pp->pkg_name == NULL) { device_printf(dev, "missing entry for package pin %s\n", p->pkg_name); continue; } if (pm != aml8726_unknown_pm && pp->pull_bits == 0x00000000) { device_printf(dev, "missing pull info for package pin %s\n", p->pkg_name); continue; } AML_PINCTRL_LOCK(sc); /* * First clear all other mux bits associated with this * package pin. This may briefly configure the pin as * GPIO ... however this should be fine since after * reset the default GPIO mode is input. */ for (cf = sc->soc.func; cf->name != NULL; cf++) for (cp = cf->pins; cp->name != NULL; cp++) { if (cp == p) continue; if (strcmp(cp->pkg_name, p->pkg_name) != 0) continue; if (cp->mux_bits == 0) continue; if (pp->aobus == false) { value = MUX_READ_4(sc, cp->mux_addr); value &= ~cp->mux_bits; MUX_WRITE_4(sc, cp->mux_addr, value); } else { value = AOMUX_READ_4(sc, cp->mux_addr); value &= ~cp->mux_bits; AOMUX_WRITE_4(sc, cp->mux_addr, value); } } /* * Now set the desired mux bits. * * In the case of GPIO there's no bits to set. */ if (p->mux_bits != 0) { if (pp->aobus == false) { value = MUX_READ_4(sc, p->mux_addr); value |= p->mux_bits; MUX_WRITE_4(sc, p->mux_addr, value); } else { value = AOMUX_READ_4(sc, p->mux_addr); value |= p->mux_bits; AOMUX_WRITE_4(sc, p->mux_addr, value); } } /* * Finally set the pull mode if it was specified. */ switch (pm) { case aml8726_enable_down_pm: case aml8726_enable_up_pm: if (pp->aobus == false) { value = PUD_READ_4(sc, pp->pull_addr); if (pm == aml8726_enable_down_pm) value &= ~pp->pull_bits; else value |= pp->pull_bits; PUD_WRITE_4(sc, pp->pull_addr, value); } else { value = AOPUD_READ_4(sc, pp->pull_addr); if (pm == aml8726_enable_down_pm) value &= ~(pp->pull_bits << 16); else value |= (pp->pull_bits << 16); AOPUD_WRITE_4(sc, pp->pull_addr, value); } /* FALLTHROUGH */ case aml8726_disable_pm: case aml8726_enable_pm: if (pp->aobus == false) { value = PEN_READ_4(sc, pp->pull_addr); if (pm == aml8726_disable_pm) value &= ~pp->pull_bits; else value |= pp->pull_bits; PEN_WRITE_4(sc, pp->pull_addr, value); } else { value = AOPEN_READ_4(sc, pp->pull_addr); if (pm == aml8726_disable_pm) value &= ~pp->pull_bits; else value |= pp->pull_bits; AOPEN_WRITE_4(sc, pp->pull_addr, value); } break; default: break; } AML_PINCTRL_UNLOCK(sc); } free(pins, M_OFWPROP); return (0); } static device_method_t aml8726_pinctrl_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aml8726_pinctrl_probe), DEVMETHOD(device_attach, aml8726_pinctrl_attach), DEVMETHOD(device_detach, aml8726_pinctrl_detach), /* fdt_pinctrl interface */ DEVMETHOD(fdt_pinctrl_configure,aml8726_pinctrl_configure_pins), DEVMETHOD_END }; static driver_t aml8726_pinctrl_driver = { "pinctrl", aml8726_pinctrl_methods, sizeof(struct aml8726_pinctrl_softc), }; static devclass_t aml8726_pinctrl_devclass; EARLY_DRIVER_MODULE(pinctrl, simplebus, aml8726_pinctrl_driver, aml8726_pinctrl_devclass, 0, 0, BUS_PASS_CPU + BUS_PASS_ORDER_LATE); Index: head/sys/arm/amlogic/aml8726/aml8726_sdxc-m8.c =================================================================== --- head/sys/arm/amlogic/aml8726/aml8726_sdxc-m8.c (revision 283182) +++ head/sys/arm/amlogic/aml8726/aml8726_sdxc-m8.c (revision 283183) @@ -1,1380 +1,1379 @@ /*- * Copyright 2015 John Wehle * 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. */ /* * Amlogic aml8726-m8 (and later) SDXC host controller driver. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" #include "mmcbr_if.h" /* * The table is sorted from highest to lowest and * last entry in the table is mark by freq == 0. */ struct { uint32_t voltage; uint32_t freq; uint32_t rx_phase; } aml8726_sdxc_clk_phases[] = { { MMC_OCR_LOW_VOLTAGE | MMC_OCR_320_330 | MMC_OCR_330_340, 100000000, 1 }, { MMC_OCR_320_330 | MMC_OCR_330_340, 45000000, 15 }, { MMC_OCR_LOW_VOLTAGE, 45000000, 11 }, { MMC_OCR_LOW_VOLTAGE | MMC_OCR_320_330 | MMC_OCR_330_340, 24999999, 15 }, { MMC_OCR_LOW_VOLTAGE | MMC_OCR_320_330 | MMC_OCR_330_340, 5000000, 23 }, { MMC_OCR_LOW_VOLTAGE | MMC_OCR_320_330 | MMC_OCR_330_340, 1000000, 55 }, { MMC_OCR_LOW_VOLTAGE | MMC_OCR_320_330 | MMC_OCR_330_340, 0, 1061 }, }; struct aml8726_sdxc_gpio { device_t dev; uint32_t pin; uint32_t pol; }; struct aml8726_sdxc_softc { device_t dev; boolean_t auto_fill_flush; struct resource *res[2]; struct mtx mtx; struct callout ch; unsigned int ref_freq; struct aml8726_sdxc_gpio pwr_en; int voltages[2]; struct aml8726_sdxc_gpio vselect; struct aml8726_sdxc_gpio card_rst; bus_dma_tag_t dmatag; bus_dmamap_t dmamap; void *ih_cookie; struct mmc_host host; int bus_busy; struct { uint32_t time; uint32_t error; } busy; struct mmc_command *cmd; }; static struct resource_spec aml8726_sdxc_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, { -1, 0 } }; #define AML_SDXC_LOCK(sc) mtx_lock(&(sc)->mtx) #define AML_SDXC_UNLOCK(sc) mtx_unlock(&(sc)->mtx) #define AML_SDXC_LOCK_ASSERT(sc) mtx_assert(&(sc)->mtx, MA_OWNED) #define AML_SDXC_LOCK_INIT(sc) \ mtx_init(&(sc)->mtx, device_get_nameunit((sc)->dev), \ "sdxc", MTX_DEF) #define AML_SDXC_LOCK_DESTROY(sc) mtx_destroy(&(sc)->mtx); #define CSR_WRITE_4(sc, reg, val) bus_write_4((sc)->res[0], reg, (val)) #define CSR_READ_4(sc, reg) bus_read_4((sc)->res[0], reg) #define CSR_BARRIER(sc, reg) bus_barrier((sc)->res[0], reg, 4, \ (BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE)) #define PIN_ON_FLAG(pol) ((pol) == 0 ? \ GPIO_PIN_LOW : GPIO_PIN_HIGH) #define PIN_OFF_FLAG(pol) ((pol) == 0 ? \ GPIO_PIN_HIGH : GPIO_PIN_LOW) #define msecs_to_ticks(ms) (((ms)*hz)/1000 + 1) static void aml8726_sdxc_timeout(void *arg); static void aml8726_sdxc_mapmem(void *arg, bus_dma_segment_t *segs, int nseg, int error) { bus_addr_t *busaddrp; /* * There should only be one bus space address since * bus_dma_tag_create was called with nsegments = 1. */ busaddrp = (bus_addr_t *)arg; *busaddrp = segs->ds_addr; } static int aml8726_sdxc_power_off(struct aml8726_sdxc_softc *sc) { if (sc->pwr_en.dev == NULL) return (0); return (GPIO_PIN_SET(sc->pwr_en.dev, sc->pwr_en.pin, PIN_OFF_FLAG(sc->pwr_en.pol))); } static int aml8726_sdxc_power_on(struct aml8726_sdxc_softc *sc) { if (sc->pwr_en.dev == NULL) return (0); return (GPIO_PIN_SET(sc->pwr_en.dev, sc->pwr_en.pin, PIN_ON_FLAG(sc->pwr_en.pol))); } static void aml8726_sdxc_soft_reset(struct aml8726_sdxc_softc *sc) { CSR_WRITE_4(sc, AML_SDXC_SOFT_RESET_REG, AML_SDXC_SOFT_RESET); CSR_BARRIER(sc, AML_SDXC_SOFT_RESET_REG); DELAY(5); } static void aml8726_sdxc_engage_dma(struct aml8726_sdxc_softc *sc) { int i; uint32_t pdmar; uint32_t sr; struct mmc_data *data; data = sc->cmd->data; if (data == NULL || data->len == 0) return; /* * Engaging the DMA hardware is recommended before writing * to AML_SDXC_SEND_REG so that the FIFOs are ready to go. * * Presumably AML_SDXC_CNTRL_REG and AML_SDXC_DMA_ADDR_REG * must be set up prior to this happening. */ pdmar = CSR_READ_4(sc, AML_SDXC_PDMA_REG); pdmar &= ~AML_SDXC_PDMA_RX_FLUSH_MODE_SW; pdmar |= AML_SDXC_PDMA_DMA_EN; if (sc->auto_fill_flush == true) { CSR_WRITE_4(sc, AML_SDXC_PDMA_REG, pdmar); CSR_BARRIER(sc, AML_SDXC_PDMA_REG); return; } if ((data->flags & MMC_DATA_READ) != 0) { pdmar |= AML_SDXC_PDMA_RX_FLUSH_MODE_SW; CSR_WRITE_4(sc, AML_SDXC_PDMA_REG, pdmar); CSR_BARRIER(sc, AML_SDXC_PDMA_REG); } else { pdmar |= AML_SDXC_PDMA_TX_FILL; CSR_WRITE_4(sc, AML_SDXC_PDMA_REG, pdmar); CSR_BARRIER(sc, AML_SDXC_PDMA_REG); /* * Wait up to 100us for data to show up. */ for (i = 0; i < 100; i++) { sr = CSR_READ_4(sc, AML_SDXC_STATUS_REG); if ((sr & AML_SDXC_STATUS_TX_CNT_MASK) != 0) break; DELAY(1); } if (i >= 100) device_printf(sc->dev, "TX FIFO fill timeout\n"); } } static void aml8726_sdxc_disengage_dma(struct aml8726_sdxc_softc *sc) { int i; uint32_t pdmar; uint32_t sr; struct mmc_data *data; data = sc->cmd->data; if (data == NULL || data->len == 0) return; pdmar = CSR_READ_4(sc, AML_SDXC_PDMA_REG); if (sc->auto_fill_flush == true) { pdmar &= ~AML_SDXC_PDMA_DMA_EN; CSR_WRITE_4(sc, AML_SDXC_PDMA_REG, pdmar); CSR_BARRIER(sc, AML_SDXC_PDMA_REG); return; } if ((data->flags & MMC_DATA_READ) != 0) { pdmar |= AML_SDXC_PDMA_RX_FLUSH_NOW; CSR_WRITE_4(sc, AML_SDXC_PDMA_REG, pdmar); CSR_BARRIER(sc, AML_SDXC_PDMA_REG); /* * Wait up to 100us for data to drain. */ for (i = 0; i < 100; i++) { sr = CSR_READ_4(sc, AML_SDXC_STATUS_REG); if ((sr & AML_SDXC_STATUS_RX_CNT_MASK) == 0) break; DELAY(1); } if (i >= 100) device_printf(sc->dev, "RX FIFO drain timeout\n"); } pdmar &= ~(AML_SDXC_PDMA_DMA_EN | AML_SDXC_PDMA_RX_FLUSH_MODE_SW); CSR_WRITE_4(sc, AML_SDXC_PDMA_REG, pdmar); CSR_BARRIER(sc, AML_SDXC_PDMA_REG); } static int aml8726_sdxc_start_command(struct aml8726_sdxc_softc *sc, struct mmc_command *cmd) { bus_addr_t baddr; uint32_t block_size; uint32_t ctlr; uint32_t ier; uint32_t sndr; uint32_t timeout; int error; struct mmc_data *data; AML_SDXC_LOCK_ASSERT(sc); if (cmd->opcode > 0x3f) return (MMC_ERR_INVALID); /* * Ensure the hardware state machine is in a known state. */ aml8726_sdxc_soft_reset(sc); sndr = cmd->opcode; if ((cmd->flags & MMC_RSP_136) != 0) { sndr |= AML_SDXC_SEND_CMD_HAS_RESP; sndr |= AML_SDXC_SEND_RESP_136; /* * According to the SD spec the 136 bit response is * used for getting the CID or CSD in which case the * CRC7 is embedded in the contents rather than being * calculated over the entire response (the controller * always checks the CRC7 over the entire response). */ sndr |= AML_SDXC_SEND_RESP_NO_CRC7_CHECK; } else if ((cmd->flags & MMC_RSP_PRESENT) != 0) sndr |= AML_SDXC_SEND_CMD_HAS_RESP; if ((cmd->flags & MMC_RSP_CRC) == 0) sndr |= AML_SDXC_SEND_RESP_NO_CRC7_CHECK; if (cmd->opcode == MMC_STOP_TRANSMISSION) sndr |= AML_SDXC_SEND_DATA_STOP; data = cmd->data; baddr = 0; ctlr = CSR_READ_4(sc, AML_SDXC_CNTRL_REG); ier = AML_SDXC_IRQ_ENABLE_STANDARD; timeout = AML_SDXC_CMD_TIMEOUT; ctlr &= ~AML_SDXC_CNTRL_PKG_LEN_MASK; if (data && data->len && (data->flags & (MMC_DATA_READ | MMC_DATA_WRITE)) != 0) { block_size = data->len; if ((data->flags & MMC_DATA_MULTI) != 0) { block_size = MMC_SECTOR_SIZE; if ((data->len % block_size) != 0) return (MMC_ERR_INVALID); } if (block_size > 512) return (MMC_ERR_INVALID); sndr |= AML_SDXC_SEND_CMD_HAS_DATA; sndr |= ((data->flags & MMC_DATA_WRITE) != 0) ? AML_SDXC_SEND_DATA_WRITE : 0; sndr |= (((data->len / block_size) - 1) << AML_SDXC_SEND_REP_PKG_CNT_SHIFT); ctlr |= ((block_size < 512) ? block_size : 0) << AML_SDXC_CNTRL_PKG_LEN_SHIFT; ier &= ~AML_SDXC_IRQ_ENABLE_RESP_OK; ier |= (sc->auto_fill_flush == true || (data->flags & MMC_DATA_WRITE) != 0) ? AML_SDXC_IRQ_ENABLE_DMA_DONE : AML_SDXC_IRQ_ENABLE_TRANSFER_DONE_OK; error = bus_dmamap_load(sc->dmatag, sc->dmamap, data->data, data->len, aml8726_sdxc_mapmem, &baddr, BUS_DMA_NOWAIT); if (error) return (MMC_ERR_NO_MEMORY); if ((data->flags & MMC_DATA_READ) != 0) { bus_dmamap_sync(sc->dmatag, sc->dmamap, BUS_DMASYNC_PREREAD); timeout = AML_SDXC_READ_TIMEOUT * (data->len / block_size); } else { bus_dmamap_sync(sc->dmatag, sc->dmamap, BUS_DMASYNC_PREWRITE); timeout = AML_SDXC_WRITE_TIMEOUT * (data->len / block_size); } } sc->cmd = cmd; cmd->error = MMC_ERR_NONE; sc->busy.time = 0; sc->busy.error = MMC_ERR_NONE; if (timeout > AML_SDXC_MAX_TIMEOUT) timeout = AML_SDXC_MAX_TIMEOUT; callout_reset(&sc->ch, msecs_to_ticks(timeout), aml8726_sdxc_timeout, sc); CSR_WRITE_4(sc, AML_SDXC_IRQ_ENABLE_REG, ier); CSR_WRITE_4(sc, AML_SDXC_CNTRL_REG, ctlr); CSR_WRITE_4(sc, AML_SDXC_DMA_ADDR_REG, (uint32_t)baddr); CSR_WRITE_4(sc, AML_SDXC_CMD_ARGUMENT_REG, cmd->arg); aml8726_sdxc_engage_dma(sc); CSR_WRITE_4(sc, AML_SDXC_SEND_REG, sndr); CSR_BARRIER(sc, AML_SDXC_SEND_REG); return (MMC_ERR_NONE); } static void aml8726_sdxc_finish_command(struct aml8726_sdxc_softc *sc, int mmc_error) { int mmc_stop_error; struct mmc_command *cmd; struct mmc_command *stop_cmd; struct mmc_data *data; AML_SDXC_LOCK_ASSERT(sc); /* Clear all interrupts since the request is no longer in flight. */ CSR_WRITE_4(sc, AML_SDXC_IRQ_STATUS_REG, AML_SDXC_IRQ_STATUS_CLEAR); CSR_BARRIER(sc, AML_SDXC_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) { /* * If the original command executed successfuly, then * the hardware will also have automatically executed * a stop command so don't bother with the one supplied * with the original request. */ if (mmc_error == MMC_ERR_NONE) { stop_cmd->error = MMC_ERR_NONE; stop_cmd->resp[0] = cmd->resp[0]; stop_cmd->resp[1] = cmd->resp[1]; stop_cmd->resp[2] = cmd->resp[2]; stop_cmd->resp[3] = cmd->resp[3]; } else { mmc_stop_error = aml8726_sdxc_start_command(sc, stop_cmd); if (mmc_stop_error == MMC_ERR_NONE) { AML_SDXC_UNLOCK(sc); return; } stop_cmd->error = mmc_stop_error; } } AML_SDXC_UNLOCK(sc); /* Execute the callback after dropping the lock. */ if (cmd->mrq != NULL) cmd->mrq->done(cmd->mrq); } static void aml8726_sdxc_timeout(void *arg) { struct aml8726_sdxc_softc *sc = (struct aml8726_sdxc_softc *)arg; /* * The command failed to complete in time so forcefully * terminate it. */ aml8726_sdxc_soft_reset(sc); /* * Ensure the command has terminated before continuing on * to things such as bus_dmamap_sync / bus_dmamap_unload. */ while ((CSR_READ_4(sc, AML_SDXC_STATUS_REG) & AML_SDXC_STATUS_BUSY) != 0) cpu_spinwait(); aml8726_sdxc_finish_command(sc, MMC_ERR_TIMEOUT); } static void aml8726_sdxc_busy_check(void *arg) { struct aml8726_sdxc_softc *sc = (struct aml8726_sdxc_softc *)arg; uint32_t sr; sc->busy.time += AML_SDXC_BUSY_POLL_INTVL; sr = CSR_READ_4(sc, AML_SDXC_STATUS_REG); if ((sr & AML_SDXC_STATUS_DAT0) == 0) { if (sc->busy.time < AML_SDXC_BUSY_TIMEOUT) { callout_reset(&sc->ch, msecs_to_ticks(AML_SDXC_BUSY_POLL_INTVL), aml8726_sdxc_busy_check, sc); AML_SDXC_UNLOCK(sc); return; } if (sc->busy.error == MMC_ERR_NONE) sc->busy.error = MMC_ERR_TIMEOUT; } aml8726_sdxc_finish_command(sc, sc->busy.error); } static void aml8726_sdxc_intr(void *arg) { struct aml8726_sdxc_softc *sc = (struct aml8726_sdxc_softc *)arg; uint32_t isr; uint32_t pdmar; uint32_t sndr; uint32_t sr; int i; int mmc_error; int start; int stop; AML_SDXC_LOCK(sc); isr = CSR_READ_4(sc, AML_SDXC_IRQ_STATUS_REG); sndr = CSR_READ_4(sc, AML_SDXC_SEND_REG); sr = CSR_READ_4(sc, AML_SDXC_STATUS_REG); if (sc->cmd == NULL) goto spurious; mmc_error = MMC_ERR_NONE; if ((isr & (AML_SDXC_IRQ_STATUS_TX_FIFO_EMPTY | AML_SDXC_IRQ_STATUS_RX_FIFO_FULL)) != 0) mmc_error = MMC_ERR_FIFO; else if ((isr & (AML_SDXC_IRQ_ENABLE_A_PKG_CRC_ERR | AML_SDXC_IRQ_ENABLE_RESP_CRC_ERR)) != 0) mmc_error = MMC_ERR_BADCRC; else if ((isr & (AML_SDXC_IRQ_ENABLE_A_PKG_TIMEOUT_ERR | AML_SDXC_IRQ_ENABLE_RESP_TIMEOUT_ERR)) != 0) mmc_error = MMC_ERR_TIMEOUT; else if ((isr & (AML_SDXC_IRQ_STATUS_RESP_OK | AML_SDXC_IRQ_STATUS_DMA_DONE | AML_SDXC_IRQ_STATUS_TRANSFER_DONE_OK)) != 0) { ; } else { spurious: /* * Clear spurious interrupts while leaving intacted any * interrupts that may have occurred after we read the * interrupt status register. */ CSR_WRITE_4(sc, AML_SDXC_IRQ_STATUS_REG, (AML_SDXC_IRQ_STATUS_CLEAR & isr)); CSR_BARRIER(sc, AML_SDXC_IRQ_STATUS_REG); AML_SDXC_UNLOCK(sc); return; } aml8726_sdxc_disengage_dma(sc); if ((sndr & AML_SDXC_SEND_CMD_HAS_RESP) != 0) { start = 0; stop = 1; if ((sndr & AML_SDXC_SEND_RESP_136) != 0) { start = 1; stop = start + 4;; } for (i = start; i < stop; i++) { pdmar = CSR_READ_4(sc, AML_SDXC_PDMA_REG); pdmar &= ~(AML_SDXC_PDMA_DMA_EN | AML_SDXC_PDMA_RESP_INDEX_MASK); pdmar |= i << AML_SDXC_PDMA_RESP_INDEX_SHIFT; CSR_WRITE_4(sc, AML_SDXC_PDMA_REG, pdmar); sc->cmd->resp[(stop - 1) - i] = CSR_READ_4(sc, AML_SDXC_CMD_ARGUMENT_REG); } } if ((sr & AML_SDXC_STATUS_BUSY) != 0 && /* * A multiblock operation may keep the hardware * busy until stop transmission is executed. */ (isr & (AML_SDXC_IRQ_STATUS_DMA_DONE | AML_SDXC_IRQ_STATUS_TRANSFER_DONE_OK)) == 0) { if (mmc_error == MMC_ERR_NONE) mmc_error = MMC_ERR_FAILED; /* * 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. */ aml8726_sdxc_soft_reset(sc); while ((CSR_READ_4(sc, AML_SDXC_STATUS_REG) & AML_SDXC_STATUS_BUSY) != 0) cpu_spinwait(); } /* * The stop command can be generated either manually or * automatically by the hardware if MISC_MANUAL_STOP_MODE * has not been set. In either case check for busy. */ if (((sc->cmd->flags & MMC_RSP_BUSY) != 0 || (sndr & AML_SDXC_SEND_INDEX_MASK) == MMC_STOP_TRANSMISSION) && (sr & AML_SDXC_STATUS_DAT0) == 0) { sc->busy.error = mmc_error; callout_reset(&sc->ch, msecs_to_ticks(AML_SDXC_BUSY_POLL_INTVL), aml8726_sdxc_busy_check, sc); CSR_WRITE_4(sc, AML_SDXC_IRQ_STATUS_REG, (AML_SDXC_IRQ_STATUS_CLEAR & isr)); CSR_BARRIER(sc, AML_SDXC_IRQ_STATUS_REG); AML_SDXC_UNLOCK(sc); return; } aml8726_sdxc_finish_command(sc, mmc_error); } static int aml8726_sdxc_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "amlogic,aml8726-sdxc-m8")) return (ENXIO); device_set_desc(dev, "Amlogic aml8726-m8 SDXC"); return (BUS_PROBE_DEFAULT); } static int aml8726_sdxc_attach(device_t dev) { struct aml8726_sdxc_softc *sc = device_get_softc(dev); char *voltages; char *voltage; int error; int nvoltages; pcell_t prop[3]; phandle_t node; ssize_t len; device_t child; uint32_t ectlr; uint32_t miscr; uint32_t pdmar; sc->dev = dev; sc->auto_fill_flush = false; pdmar = AML_SDXC_PDMA_DMA_URGENT | (49 << AML_SDXC_PDMA_TX_THOLD_SHIFT) | (7 << AML_SDXC_PDMA_RX_THOLD_SHIFT) | (15 << AML_SDXC_PDMA_RD_BURST_SHIFT) | (7 << AML_SDXC_PDMA_WR_BURST_SHIFT); miscr = (2 << AML_SDXC_MISC_WCRC_OK_PAT_SHIFT) | (5 << AML_SDXC_MISC_WCRC_ERR_PAT_SHIFT); ectlr = (12 << AML_SDXC_ENH_CNTRL_SDIO_IRQ_PERIOD_SHIFT); /* * Certain bitfields are dependent on the hardware revision. */ switch (aml8726_soc_hw_rev) { case AML_SOC_HW_REV_M8: switch (aml8726_soc_metal_rev) { case AML_SOC_M8_METAL_REV_M2_A: sc->auto_fill_flush = true; miscr |= (6 << AML_SDXC_MISC_TXSTART_THOLD_SHIFT); ectlr |= (64 << AML_SDXC_ENH_CNTRL_RX_FULL_THOLD_SHIFT) | AML_SDXC_ENH_CNTRL_WR_RESP_MODE_SKIP_M8M2; break; default: miscr |= (7 << AML_SDXC_MISC_TXSTART_THOLD_SHIFT); ectlr |= (63 << AML_SDXC_ENH_CNTRL_RX_FULL_THOLD_SHIFT) | AML_SDXC_ENH_CNTRL_DMA_NO_WR_RESP_CHECK_M8 | (255 << AML_SDXC_ENH_CNTRL_RX_TIMEOUT_SHIFT_M8); break; } break; case AML_SOC_HW_REV_M8B: miscr |= (7 << AML_SDXC_MISC_TXSTART_THOLD_SHIFT); ectlr |= (63 << AML_SDXC_ENH_CNTRL_RX_FULL_THOLD_SHIFT) | AML_SDXC_ENH_CNTRL_DMA_NO_WR_RESP_CHECK_M8 | (255 << AML_SDXC_ENH_CNTRL_RX_TIMEOUT_SHIFT_M8); break; default: device_printf(dev, "unsupported SoC\n"); return (ENXIO); /* NOTREACHED */ - break; } node = ofw_bus_get_node(dev); len = OF_getencprop(node, "clock-frequency", prop, sizeof(prop)); if ((len / sizeof(prop[0])) != 1 || prop[0] == 0) { device_printf(dev, "missing clock-frequency attribute in FDT\n"); return (ENXIO); } sc->ref_freq = prop[0]; sc->pwr_en.dev = NULL; len = OF_getencprop(node, "mmc-pwr-en", prop, sizeof(prop)); if (len > 0) { if ((len / sizeof(prop[0])) == 3) { sc->pwr_en.dev = OF_device_from_xref(prop[0]); sc->pwr_en.pin = prop[1]; sc->pwr_en.pol = prop[2]; } if (sc->pwr_en.dev == NULL) { device_printf(dev, "unable to process mmc-pwr-en attribute in FDT\n"); return (ENXIO); } /* Turn off power and then configure the output driver. */ if (aml8726_sdxc_power_off(sc) != 0 || GPIO_PIN_SETFLAGS(sc->pwr_en.dev, sc->pwr_en.pin, GPIO_PIN_OUTPUT) != 0) { device_printf(dev, "could not use gpio to control power\n"); return (ENXIO); } } len = OF_getprop_alloc(node, "mmc-voltages", sizeof(char), (void **)&voltages); if (len < 0) { device_printf(dev, "missing mmc-voltages attribute in FDT\n"); return (ENXIO); } sc->voltages[0] = 0; sc->voltages[1] = 0; voltage = voltages; nvoltages = 0; while (len && nvoltages < 2) { if (strncmp("1.8", voltage, len) == 0) sc->voltages[nvoltages] = MMC_OCR_LOW_VOLTAGE; else if (strncmp("3.3", voltage, len) == 0) sc->voltages[nvoltages] = MMC_OCR_320_330 | MMC_OCR_330_340; else { device_printf(dev, "unknown voltage attribute %.*s in FDT\n", len, voltage); free(voltages, M_OFWPROP); return (ENXIO); } nvoltages++; /* queue up next string */ while (*voltage && len) { voltage++; len--; } if (len) { voltage++; len--; } } free(voltages, M_OFWPROP); sc->vselect.dev = NULL; len = OF_getencprop(node, "mmc-vselect", prop, sizeof(prop)); if (len > 0) { if ((len / sizeof(prop[0])) == 2) { sc->vselect.dev = OF_device_from_xref(prop[0]); sc->vselect.pin = prop[1]; sc->vselect.pol = 1; } if (sc->vselect.dev == NULL) { device_printf(dev, "unable to process mmc-vselect attribute in FDT\n"); return (ENXIO); } /* * With the power off select voltage 0 and then * configure the output driver. */ if (GPIO_PIN_SET(sc->vselect.dev, sc->vselect.pin, 0) != 0 || GPIO_PIN_SETFLAGS(sc->vselect.dev, sc->vselect.pin, GPIO_PIN_OUTPUT) != 0) { device_printf(dev, "could not use gpio to set voltage\n"); return (ENXIO); } } if (nvoltages == 0) { device_printf(dev, "no voltages in FDT\n"); return (ENXIO); } else if (nvoltages == 1 && sc->vselect.dev != NULL) { device_printf(dev, "only one voltage in FDT\n"); return (ENXIO); } else if (nvoltages == 2 && sc->vselect.dev == NULL) { device_printf(dev, "too many voltages in FDT\n"); return (ENXIO); } sc->card_rst.dev = NULL; len = OF_getencprop(node, "mmc-rst", prop, sizeof(prop)); if (len > 0) { if ((len / sizeof(prop[0])) == 3) { sc->card_rst.dev = OF_device_from_xref(prop[0]); sc->card_rst.pin = prop[1]; sc->card_rst.pol = prop[2]; } if (sc->card_rst.dev == NULL) { device_printf(dev, "unable to process mmc-rst attribute in FDT\n"); return (ENXIO); } } if (bus_alloc_resources(dev, aml8726_sdxc_spec, sc->res)) { device_printf(dev, "could not allocate resources for device\n"); return (ENXIO); } AML_SDXC_LOCK_INIT(sc); error = bus_dma_tag_create(bus_get_dma_tag(dev), AML_SDXC_ALIGN_DMA, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, AML_SDXC_MAX_DMA, 1, AML_SDXC_MAX_DMA, 0, NULL, NULL, &sc->dmatag); if (error) goto fail; error = bus_dmamap_create(sc->dmatag, 0, &sc->dmamap); if (error) goto fail; error = bus_setup_intr(dev, sc->res[1], INTR_TYPE_MISC | INTR_MPSAFE, NULL, aml8726_sdxc_intr, sc, &sc->ih_cookie); if (error) { device_printf(dev, "could not setup interrupt handler\n"); goto fail; } callout_init_mtx(&sc->ch, &sc->mtx, CALLOUT_RETURNUNLOCKED); sc->host.f_min = 200000; sc->host.f_max = 100000000; sc->host.host_ocr = sc->voltages[0] | sc->voltages[1]; sc->host.caps = MMC_CAP_8_BIT_DATA | MMC_CAP_4_BIT_DATA | MMC_CAP_HSPEED; aml8726_sdxc_soft_reset(sc); CSR_WRITE_4(sc, AML_SDXC_PDMA_REG, pdmar); CSR_WRITE_4(sc, AML_SDXC_MISC_REG, miscr); CSR_WRITE_4(sc, AML_SDXC_ENH_CNTRL_REG, ectlr); child = device_add_child(dev, "mmc", -1); if (!child) { device_printf(dev, "could not add mmc\n"); error = ENXIO; goto fail; } error = device_probe_and_attach(child); if (error) { device_printf(dev, "could not attach mmc\n"); goto fail; } return (0); fail: if (sc->ih_cookie) bus_teardown_intr(dev, sc->res[1], sc->ih_cookie); if (sc->dmamap) bus_dmamap_destroy(sc->dmatag, sc->dmamap); if (sc->dmatag) bus_dma_tag_destroy(sc->dmatag); AML_SDXC_LOCK_DESTROY(sc); (void)aml8726_sdxc_power_off(sc); bus_release_resources(dev, aml8726_sdxc_spec, sc->res); return (error); } static int aml8726_sdxc_detach(device_t dev) { struct aml8726_sdxc_softc *sc = device_get_softc(dev); AML_SDXC_LOCK(sc); if (sc->cmd != NULL) { AML_SDXC_UNLOCK(sc); return (EBUSY); } /* * Turn off the power, reset the hardware state machine, * and disable the interrupts. */ aml8726_sdxc_power_off(sc); aml8726_sdxc_soft_reset(sc); CSR_WRITE_4(sc, AML_SDXC_IRQ_ENABLE_REG, 0); AML_SDXC_UNLOCK(sc); bus_generic_detach(dev); bus_teardown_intr(dev, sc->res[1], sc->ih_cookie); bus_dmamap_destroy(sc->dmatag, sc->dmamap); bus_dma_tag_destroy(sc->dmatag); AML_SDXC_LOCK_DESTROY(sc); bus_release_resources(dev, aml8726_sdxc_spec, sc->res); return (0); } static int aml8726_sdxc_shutdown(device_t dev) { struct aml8726_sdxc_softc *sc = device_get_softc(dev); /* * Turn off the power, reset the hardware state machine, * and disable the interrupts. */ aml8726_sdxc_power_off(sc); aml8726_sdxc_soft_reset(sc); CSR_WRITE_4(sc, AML_SDXC_IRQ_ENABLE_REG, 0); return (0); } static int aml8726_sdxc_update_ios(device_t bus, device_t child) { struct aml8726_sdxc_softc *sc = device_get_softc(bus); struct mmc_ios *ios = &sc->host.ios; unsigned int divisor; int error; int i; uint32_t cctlr; uint32_t clk2r; uint32_t ctlr; uint32_t freq; ctlr = (7 << AML_SDXC_CNTRL_TX_ENDIAN_SHIFT) | (7 << AML_SDXC_CNTRL_RX_ENDIAN_SHIFT) | (0xf << AML_SDXC_CNTRL_RX_PERIOD_SHIFT) | (0x7f << AML_SDXC_CNTRL_RX_TIMEOUT_SHIFT); switch (ios->bus_width) { case bus_width_8: ctlr |= AML_SDXC_CNTRL_BUS_WIDTH_8; break; case bus_width_4: ctlr |= AML_SDXC_CNTRL_BUS_WIDTH_4; break; case bus_width_1: ctlr |= AML_SDXC_CNTRL_BUS_WIDTH_1; break; default: return (EINVAL); } CSR_WRITE_4(sc, AML_SDXC_CNTRL_REG, ctlr); /* * Disable clocks and then clock module prior to setting desired values. */ cctlr = CSR_READ_4(sc, AML_SDXC_CLK_CNTRL_REG); cctlr &= ~(AML_SDXC_CLK_CNTRL_TX_CLK_EN | AML_SDXC_CLK_CNTRL_RX_CLK_EN | AML_SDXC_CLK_CNTRL_SD_CLK_EN); CSR_WRITE_4(sc, AML_SDXC_CLK_CNTRL_REG, cctlr); CSR_BARRIER(sc, AML_SDXC_CLK_CNTRL_REG); cctlr &= ~AML_SDXC_CLK_CNTRL_CLK_MODULE_EN; CSR_WRITE_4(sc, AML_SDXC_CLK_CNTRL_REG, cctlr); CSR_BARRIER(sc, AML_SDXC_CLK_CNTRL_REG); /* * aml8726-m8 * * Clock select 1 fclk_div2 (1.275 GHz) */ cctlr &= ~AML_SDXC_CLK_CNTRL_CLK_SEL_MASK; cctlr |= (1 << AML_SDXC_CLK_CNTRL_CLK_SEL_SHIFT); divisor = sc->ref_freq / ios->clock - 1; if (divisor == 0 || divisor == -1) divisor = 1; if ((sc->ref_freq / (divisor + 1)) > ios->clock) divisor += 1; if (divisor > (AML_SDXC_CLK_CNTRL_CLK_DIV_MASK >> AML_SDXC_CLK_CNTRL_CLK_DIV_SHIFT)) divisor = AML_SDXC_CLK_CNTRL_CLK_DIV_MASK >> AML_SDXC_CLK_CNTRL_CLK_DIV_SHIFT; cctlr &= ~AML_SDXC_CLK_CNTRL_CLK_DIV_MASK; cctlr |= divisor << AML_SDXC_CLK_CNTRL_CLK_DIV_SHIFT; cctlr &= ~AML_SDXC_CLK_CNTRL_MEM_PWR_MASK; cctlr |= AML_SDXC_CLK_CNTRL_MEM_PWR_ON; CSR_WRITE_4(sc, AML_SDXC_CLK_CNTRL_REG, cctlr); CSR_BARRIER(sc, AML_SDXC_CLK_CNTRL_REG); /* * Enable clock module and then clocks after setting desired values. */ cctlr |= AML_SDXC_CLK_CNTRL_CLK_MODULE_EN; CSR_WRITE_4(sc, AML_SDXC_CLK_CNTRL_REG, cctlr); CSR_BARRIER(sc, AML_SDXC_CLK_CNTRL_REG); cctlr |= AML_SDXC_CLK_CNTRL_TX_CLK_EN | AML_SDXC_CLK_CNTRL_RX_CLK_EN | AML_SDXC_CLK_CNTRL_SD_CLK_EN; CSR_WRITE_4(sc, AML_SDXC_CLK_CNTRL_REG, cctlr); CSR_BARRIER(sc, AML_SDXC_CLK_CNTRL_REG); freq = sc->ref_freq / divisor; for (i = 0; aml8726_sdxc_clk_phases[i].voltage; i++) { if ((aml8726_sdxc_clk_phases[i].voltage & (1 << ios->vdd)) != 0 && freq > aml8726_sdxc_clk_phases[i].freq) break; if (aml8726_sdxc_clk_phases[i].freq == 0) break; } clk2r = (1 << AML_SDXC_CLK2_SD_PHASE_SHIFT) | (aml8726_sdxc_clk_phases[i].rx_phase << AML_SDXC_CLK2_RX_PHASE_SHIFT); CSR_WRITE_4(sc, AML_SDXC_CLK2_REG, clk2r); CSR_BARRIER(sc, AML_SDXC_CLK2_REG); error = 0; switch (ios->power_mode) { case power_up: /* * Configure and power on the regulator so that the * voltage stabilizes prior to powering on the card. */ if (sc->vselect.dev != NULL) { for (i = 0; i < 2; i++) if ((sc->voltages[i] & (1 << ios->vdd)) != 0) break; if (i >= 2) return (EINVAL); error = GPIO_PIN_SET(sc->vselect.dev, sc->vselect.pin, i); } break; case power_on: error = aml8726_sdxc_power_on(sc); if (error) break; if (sc->card_rst.dev != NULL) { if (GPIO_PIN_SET(sc->card_rst.dev, sc->card_rst.pin, PIN_ON_FLAG(sc->card_rst.pol)) != 0 || GPIO_PIN_SETFLAGS(sc->card_rst.dev, sc->card_rst.pin, GPIO_PIN_OUTPUT) != 0) error = ENXIO; DELAY(5); if (GPIO_PIN_SET(sc->card_rst.dev, sc->card_rst.pin, PIN_OFF_FLAG(sc->card_rst.pol)) != 0) error = ENXIO; DELAY(5); if (error) { device_printf(sc->dev, "could not use gpio to reset card\n"); break; } } break; case power_off: error = aml8726_sdxc_power_off(sc); break; default: return (EINVAL); } return (error); } static int aml8726_sdxc_request(device_t bus, device_t child, struct mmc_request *req) { struct aml8726_sdxc_softc *sc = device_get_softc(bus); int mmc_error; AML_SDXC_LOCK(sc); if (sc->cmd != NULL) { AML_SDXC_UNLOCK(sc); return (EBUSY); } mmc_error = aml8726_sdxc_start_command(sc, req->cmd); AML_SDXC_UNLOCK(sc); /* Execute the callback after dropping the lock. */ if (mmc_error != MMC_ERR_NONE) { req->cmd->error = mmc_error; req->done(req); } return (0); } static int aml8726_sdxc_read_ivar(device_t bus, device_t child, int which, uintptr_t *result) { struct aml8726_sdxc_softc *sc = device_get_softc(bus); switch (which) { case MMCBR_IVAR_BUS_MODE: *(int *)result = sc->host.ios.bus_mode; break; case MMCBR_IVAR_BUS_WIDTH: *(int *)result = sc->host.ios.bus_width; break; case MMCBR_IVAR_CHIP_SELECT: *(int *)result = sc->host.ios.chip_select; break; case MMCBR_IVAR_CLOCK: *(int *)result = sc->host.ios.clock; break; case MMCBR_IVAR_F_MIN: *(int *)result = sc->host.f_min; break; case MMCBR_IVAR_F_MAX: *(int *)result = sc->host.f_max; break; case MMCBR_IVAR_HOST_OCR: *(int *)result = sc->host.host_ocr; break; case MMCBR_IVAR_MODE: *(int *)result = sc->host.mode; break; case MMCBR_IVAR_OCR: *(int *)result = sc->host.ocr; break; case MMCBR_IVAR_POWER_MODE: *(int *)result = sc->host.ios.power_mode; break; case MMCBR_IVAR_VDD: *(int *)result = sc->host.ios.vdd; break; case MMCBR_IVAR_CAPS: *(int *)result = sc->host.caps; break; case MMCBR_IVAR_MAX_DATA: *(int *)result = AML_SDXC_MAX_DMA / MMC_SECTOR_SIZE; break; default: return (EINVAL); } return (0); } static int aml8726_sdxc_write_ivar(device_t bus, device_t child, int which, uintptr_t value) { struct aml8726_sdxc_softc *sc = device_get_softc(bus); switch (which) { case MMCBR_IVAR_BUS_MODE: sc->host.ios.bus_mode = value; break; case MMCBR_IVAR_BUS_WIDTH: sc->host.ios.bus_width = value; break; case MMCBR_IVAR_CHIP_SELECT: sc->host.ios.chip_select = value; break; case MMCBR_IVAR_CLOCK: sc->host.ios.clock = value; break; case MMCBR_IVAR_MODE: sc->host.mode = value; break; case MMCBR_IVAR_OCR: sc->host.ocr = value; break; case MMCBR_IVAR_POWER_MODE: sc->host.ios.power_mode = value; break; case MMCBR_IVAR_VDD: sc->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: default: return (EINVAL); } return (0); } static int aml8726_sdxc_get_ro(device_t bus, device_t child) { return (0); } static int aml8726_sdxc_acquire_host(device_t bus, device_t child) { struct aml8726_sdxc_softc *sc = device_get_softc(bus); AML_SDXC_LOCK(sc); while (sc->bus_busy) mtx_sleep(sc, &sc->mtx, PZERO, "sdxc", hz / 5); sc->bus_busy++; AML_SDXC_UNLOCK(sc); return (0); } static int aml8726_sdxc_release_host(device_t bus, device_t child) { struct aml8726_sdxc_softc *sc = device_get_softc(bus); AML_SDXC_LOCK(sc); sc->bus_busy--; wakeup(sc); AML_SDXC_UNLOCK(sc); return (0); } static device_method_t aml8726_sdxc_methods[] = { /* Device interface */ DEVMETHOD(device_probe, aml8726_sdxc_probe), DEVMETHOD(device_attach, aml8726_sdxc_attach), DEVMETHOD(device_detach, aml8726_sdxc_detach), DEVMETHOD(device_shutdown, aml8726_sdxc_shutdown), /* Bus interface */ DEVMETHOD(bus_read_ivar, aml8726_sdxc_read_ivar), DEVMETHOD(bus_write_ivar, aml8726_sdxc_write_ivar), /* MMC bridge interface */ DEVMETHOD(mmcbr_update_ios, aml8726_sdxc_update_ios), DEVMETHOD(mmcbr_request, aml8726_sdxc_request), DEVMETHOD(mmcbr_get_ro, aml8726_sdxc_get_ro), DEVMETHOD(mmcbr_acquire_host, aml8726_sdxc_acquire_host), DEVMETHOD(mmcbr_release_host, aml8726_sdxc_release_host), DEVMETHOD_END }; static driver_t aml8726_sdxc_driver = { "aml8726_sdxc", aml8726_sdxc_methods, sizeof(struct aml8726_sdxc_softc), }; static devclass_t aml8726_sdxc_devclass; DRIVER_MODULE(aml8726_sdxc, simplebus, aml8726_sdxc_driver, aml8726_sdxc_devclass, 0, 0); MODULE_DEPEND(aml8726_sdxc, aml8726_gpio, 1, 1, 1);