diff options
Diffstat (limited to 'sys/arm/lpc/lpc_mmc.c')
-rw-r--r-- | sys/arm/lpc/lpc_mmc.c | 777 |
1 files changed, 777 insertions, 0 deletions
diff --git a/sys/arm/lpc/lpc_mmc.c b/sys/arm/lpc/lpc_mmc.c new file mode 100644 index 0000000..5a789f6 --- /dev/null +++ b/sys/arm/lpc/lpc_mmc.c @@ -0,0 +1,777 @@ +/*- + * Copyright (c) 2011 Jakub Wojciech Klama <jceel@FreeBSD.org> + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bio.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/endian.h> +#include <sys/kernel.h> +#include <sys/kthread.h> +#include <sys/lock.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/mutex.h> +#include <sys/queue.h> +#include <sys/resource.h> +#include <sys/rman.h> +#include <sys/time.h> +#include <sys/timetc.h> +#include <sys/watchdog.h> + +#include <sys/kdb.h> + +#include <machine/bus.h> +#include <machine/cpu.h> +#include <machine/cpufunc.h> +#include <machine/resource.h> +#include <machine/frame.h> +#include <machine/intr.h> + +#include <dev/ofw/ofw_bus.h> +#include <dev/ofw/ofw_bus_subr.h> + +#include <dev/mmc/bridge.h> +#include <dev/mmc/mmcreg.h> +#include <dev/mmc/mmcbrvar.h> + +#include <arm/lpc/lpcreg.h> +#include <arm/lpc/lpcvar.h> + +#define DEBUG +#undef DEBUG + +#ifdef DEBUG +#define debugf(fmt, args...) do { printf("%s(): ", __func__); \ + printf(fmt,##args); } while (0) +#else +#define debugf(fmt, args...) +#endif + +struct lpc_mmc_dmamap_arg { + bus_addr_t lm_dma_busaddr; +}; + +struct lpc_mmc_softc { + device_t lm_dev; + struct mtx lm_mtx; + struct resource * lm_mem_res; + struct resource * lm_irq_res; + bus_space_tag_t lm_bst; + bus_space_handle_t lm_bsh; + void * lm_intrhand; + struct mmc_host lm_host; + struct mmc_request * lm_req; + struct mmc_data * lm_data; + uint32_t lm_flags; +#define LPC_SD_FLAGS_IGNORECRC (1 << 0) + int lm_xfer_direction; +#define DIRECTION_READ 0 +#define DIRECTION_WRITE 1 + int lm_xfer_done; + int lm_bus_busy; + bus_dma_tag_t lm_dma_tag; + bus_dmamap_t lm_dma_map; + bus_addr_t lm_buffer_phys; + void * lm_buffer; +}; + +#define LPC_SD_MAX_BLOCKSIZE 1024 +/* XXX */ +#define LPC_MMC_DMACH_READ 1 +#define LPC_MMC_DMACH_WRITE 0 + + +static int lpc_mmc_probe(device_t); +static int lpc_mmc_attach(device_t); +static int lpc_mmc_detach(device_t); +static void lpc_mmc_intr(void *); + +static void lpc_mmc_cmd(struct lpc_mmc_softc *, struct mmc_command *); +static void lpc_mmc_setup_xfer(struct lpc_mmc_softc *, struct mmc_data *); + +static int lpc_mmc_update_ios(device_t, device_t); +static int lpc_mmc_request(device_t, device_t, struct mmc_request *); +static int lpc_mmc_get_ro(device_t, device_t); +static int lpc_mmc_acquire_host(device_t, device_t); +static int lpc_mmc_release_host(device_t, device_t); + +static void lpc_mmc_dma_rxfinish(void *); +static void lpc_mmc_dma_rxerror(void *); +static void lpc_mmc_dma_txfinish(void *); +static void lpc_mmc_dma_txerror(void *); + +static void lpc_mmc_dmamap_cb(void *, bus_dma_segment_t *, int, int); + +#define lpc_mmc_lock(_sc) \ + mtx_lock(&_sc->lm_mtx); +#define lpc_mmc_unlock(_sc) \ + mtx_unlock(&_sc->lm_mtx); +#define lpc_mmc_read_4(_sc, _reg) \ + bus_space_read_4(_sc->lm_bst, _sc->lm_bsh, _reg) +#define lpc_mmc_write_4(_sc, _reg, _value) \ + bus_space_write_4(_sc->lm_bst, _sc->lm_bsh, _reg, _value) + +static struct lpc_dmac_channel_config lpc_mmc_dma_rxconf = { + .ldc_fcntl = LPC_DMAC_FLOW_D_P2M, + .ldc_src_periph = LPC_DMAC_SD_ID, + .ldc_src_width = LPC_DMAC_CH_CONTROL_WIDTH_4, + .ldc_src_incr = 0, + .ldc_src_burst = LPC_DMAC_CH_CONTROL_BURST_8, + .ldc_dst_periph = LPC_DMAC_SD_ID, + .ldc_dst_width = LPC_DMAC_CH_CONTROL_WIDTH_4, + .ldc_dst_incr = 1, + .ldc_dst_burst = LPC_DMAC_CH_CONTROL_BURST_8, + .ldc_success_handler = lpc_mmc_dma_rxfinish, + .ldc_error_handler = lpc_mmc_dma_rxerror, +}; + +static struct lpc_dmac_channel_config lpc_mmc_dma_txconf = { + .ldc_fcntl = LPC_DMAC_FLOW_P_M2P, + .ldc_src_periph = LPC_DMAC_SD_ID, + .ldc_src_width = LPC_DMAC_CH_CONTROL_WIDTH_4, + .ldc_src_incr = 1, + .ldc_src_burst = LPC_DMAC_CH_CONTROL_BURST_8, + .ldc_dst_periph = LPC_DMAC_SD_ID, + .ldc_dst_width = LPC_DMAC_CH_CONTROL_WIDTH_4, + .ldc_dst_incr = 0, + .ldc_dst_burst = LPC_DMAC_CH_CONTROL_BURST_8, + .ldc_success_handler = lpc_mmc_dma_txfinish, + .ldc_error_handler = lpc_mmc_dma_txerror, +}; + +static int +lpc_mmc_probe(device_t dev) +{ + if (!ofw_bus_is_compatible(dev, "lpc,mmc")) + return (ENXIO); + + device_set_desc(dev, "LPC32x0 MMC/SD controller"); + return (BUS_PROBE_DEFAULT); +} + +static int +lpc_mmc_attach(device_t dev) +{ + struct lpc_mmc_softc *sc = device_get_softc(dev); + struct lpc_mmc_dmamap_arg ctx; + device_t child; + int rid, err; + + sc->lm_dev = dev; + sc->lm_req = NULL; + + mtx_init(&sc->lm_mtx, "lpcmmc", "mmc", MTX_DEF); + + rid = 0; + sc->lm_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (!sc->lm_mem_res) { + device_printf(dev, "cannot allocate memory window\n"); + return (ENXIO); + } + + sc->lm_bst = rman_get_bustag(sc->lm_mem_res); + sc->lm_bsh = rman_get_bushandle(sc->lm_mem_res); + + debugf("virtual register space: 0x%08lx\n", sc->lm_bsh); + + rid = 0; + sc->lm_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_ACTIVE); + if (!sc->lm_irq_res) { + device_printf(dev, "cannot allocate interrupt\n"); + bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->lm_mem_res); + return (ENXIO); + } + + if (bus_setup_intr(dev, sc->lm_irq_res, INTR_TYPE_MISC | INTR_MPSAFE, + NULL, lpc_mmc_intr, sc, &sc->lm_intrhand)) + { + bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->lm_mem_res); + bus_release_resource(dev, SYS_RES_IRQ, 0, sc->lm_irq_res); + device_printf(dev, "cannot setup interrupt handler\n"); + return (ENXIO); + } + + sc->lm_host.f_min = 312500; + sc->lm_host.f_max = 2500000; + sc->lm_host.host_ocr = MMC_OCR_300_310 | MMC_OCR_310_320 | + MMC_OCR_320_330 | MMC_OCR_330_340; +#if 0 + sc->lm_host.caps = MMC_CAP_4_BIT_DATA; +#endif + + lpc_pwr_write(dev, LPC_CLKPWR_MS_CTRL, + LPC_CLKPWR_MS_CTRL_CLOCK_EN | LPC_CLKPWR_MS_CTRL_SD_CLOCK | 1); + lpc_mmc_write_4(sc, LPC_SD_POWER, LPC_SD_POWER_CTRL_ON); + + device_set_ivars(dev, &sc->lm_host); + + child = device_add_child(dev, "mmc", -1); + if (!child) { + device_printf(dev, "attaching MMC bus failed!\n"); + bus_teardown_intr(dev, sc->lm_irq_res, sc->lm_intrhand); + bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->lm_mem_res); + bus_release_resource(dev, SYS_RES_IRQ, 0, sc->lm_irq_res); + return (ENXIO); + } + + /* Alloc DMA memory */ + err = bus_dma_tag_create( + bus_get_dma_tag(sc->lm_dev), + 4, 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + LPC_SD_MAX_BLOCKSIZE, 1, /* maxsize, nsegments */ + LPC_SD_MAX_BLOCKSIZE, 0, /* maxsegsize, flags */ + NULL, NULL, /* lockfunc, lockarg */ + &sc->lm_dma_tag); + + err = bus_dmamem_alloc(sc->lm_dma_tag, (void **)&sc->lm_buffer, + 0, &sc->lm_dma_map); + if (err) { + device_printf(dev, "cannot allocate framebuffer\n"); + goto fail; + } + + err = bus_dmamap_load(sc->lm_dma_tag, sc->lm_dma_map, sc->lm_buffer, + LPC_SD_MAX_BLOCKSIZE, lpc_mmc_dmamap_cb, &ctx, BUS_DMA_NOWAIT); + if (err) { + device_printf(dev, "cannot load DMA map\n"); + goto fail; + } + + sc->lm_buffer_phys = ctx.lm_dma_busaddr; + + lpc_mmc_dma_rxconf.ldc_handler_arg = (void *)sc; + err = lpc_dmac_config_channel(dev, LPC_MMC_DMACH_READ, &lpc_mmc_dma_rxconf); + if (err) { + device_printf(dev, "cannot allocate RX DMA channel\n"); + goto fail; + } + + + lpc_mmc_dma_txconf.ldc_handler_arg = (void *)sc; + err = lpc_dmac_config_channel(dev, LPC_MMC_DMACH_WRITE, &lpc_mmc_dma_txconf); + if (err) { + device_printf(dev, "cannot allocate TX DMA channel\n"); + goto fail; + } + + bus_generic_probe(dev); + bus_generic_attach(dev); + + return (0); + +fail: + if (sc->lm_intrhand) + bus_teardown_intr(dev, sc->lm_irq_res, sc->lm_intrhand); + if (sc->lm_irq_res) + bus_release_resource(dev, SYS_RES_IRQ, 0, sc->lm_irq_res); + if (sc->lm_mem_res) + bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->lm_mem_res); + return (err); +} + +static int +lpc_mmc_detach(device_t dev) +{ + return (EBUSY); +} + +static void +lpc_mmc_intr(void *arg) +{ + struct lpc_mmc_softc *sc = (struct lpc_mmc_softc *)arg; + struct mmc_command *cmd; + uint32_t status; + + status = lpc_mmc_read_4(sc, LPC_SD_STATUS); + + debugf("interrupt: 0x%08x\n", status); + + if (status & LPC_SD_STATUS_CMDCRCFAIL) { + cmd = sc->lm_req->cmd; + cmd->error = sc->lm_flags & LPC_SD_FLAGS_IGNORECRC + ? MMC_ERR_NONE : MMC_ERR_BADCRC; + cmd->resp[0] = lpc_mmc_read_4(sc, LPC_SD_RESP0); + sc->lm_req->done(sc->lm_req); + sc->lm_req = NULL; + lpc_mmc_write_4(sc, LPC_SD_CLEAR, LPC_SD_STATUS_CMDCRCFAIL); + } + + if (status & LPC_SD_STATUS_CMDACTIVE) + { + debugf("command active\n"); + cmd = sc->lm_req->cmd; + cmd->resp[0] = lpc_mmc_read_4(sc, LPC_SD_RESP0); + sc->lm_req->done(sc->lm_req); + sc->lm_req = NULL; + } + + if (status & LPC_SD_STATUS_DATATIMEOUT) { + device_printf(sc->lm_dev, "data timeout\n"); + lpc_mmc_write_4(sc, LPC_SD_CLEAR, LPC_SD_STATUS_DATATIMEOUT); + } + + if (status & LPC_SD_STATUS_TXUNDERRUN) { + device_printf(sc->lm_dev, "TX underrun\n"); + lpc_mmc_write_4(sc, LPC_SD_CLEAR, LPC_SD_STATUS_TXUNDERRUN); + } + + if (status & LPC_SD_STATUS_CMDRESPEND) { + debugf("command response\n"); + cmd = sc->lm_req->cmd; + + if (cmd->flags & MMC_RSP_136) { + cmd->resp[3] = lpc_mmc_read_4(sc, LPC_SD_RESP3); + cmd->resp[2] = lpc_mmc_read_4(sc, LPC_SD_RESP2); + cmd->resp[1] = lpc_mmc_read_4(sc, LPC_SD_RESP1); + } + + cmd->resp[0] = lpc_mmc_read_4(sc, LPC_SD_RESP0); + cmd->error = MMC_ERR_NONE; + + if (cmd->data && (cmd->data->flags & MMC_DATA_WRITE)) + lpc_mmc_setup_xfer(sc, sc->lm_req->cmd->data); + + if (!cmd->data) { + sc->lm_req->done(sc->lm_req); + sc->lm_req = NULL; + } + + lpc_mmc_write_4(sc, LPC_SD_CLEAR, LPC_SD_STATUS_CMDRESPEND); + } + + if (status & LPC_SD_STATUS_CMDSENT) { + debugf("command sent\n"); + cmd = sc->lm_req->cmd; + cmd->error = MMC_ERR_NONE; + sc->lm_req->done(sc->lm_req); + sc->lm_req = NULL; + lpc_mmc_write_4(sc, LPC_SD_CLEAR, LPC_SD_STATUS_CMDSENT); + } + + if (status & LPC_SD_STATUS_DATAEND) { + if (sc->lm_xfer_direction == DIRECTION_READ) + lpc_dmac_start_burst(sc->lm_dev, LPC_DMAC_SD_ID); + + lpc_mmc_write_4(sc, LPC_SD_CLEAR, LPC_SD_STATUS_DATAEND); + } + + if (status & LPC_SD_STATUS_CMDTIMEOUT) { + device_printf(sc->lm_dev, "command response timeout\n"); + cmd = sc->lm_req->cmd; + cmd->error = MMC_ERR_TIMEOUT; + sc->lm_req->done(sc->lm_req); + sc->lm_req = NULL; + lpc_mmc_write_4(sc, LPC_SD_CLEAR, LPC_SD_STATUS_CMDTIMEOUT); + return; + } + + if (status & LPC_SD_STATUS_STARTBITERR) { + device_printf(sc->lm_dev, "start bit error\n"); + lpc_mmc_write_4(sc, LPC_SD_CLEAR, LPC_SD_STATUS_STARTBITERR); + } + + if (status & LPC_SD_STATUS_DATACRCFAIL) { + device_printf(sc->lm_dev, "data CRC error\n"); + debugf("data buffer: %p\n", sc->lm_buffer); + cmd = sc->lm_req->cmd; + cmd->error = MMC_ERR_BADCRC; + sc->lm_req->done(sc->lm_req); + sc->lm_req = NULL; + + if (sc->lm_xfer_direction == DIRECTION_READ) + lpc_dmac_start_burst(sc->lm_dev, LPC_DMAC_SD_ID); + + lpc_mmc_write_4(sc, LPC_SD_CLEAR, LPC_SD_STATUS_DATACRCFAIL); + } + + if (status & LPC_SD_STATUS_DATABLOCKEND) { + debugf("data block end\n"); + if (sc->lm_xfer_direction == DIRECTION_READ) + memcpy(sc->lm_data->data, sc->lm_buffer, sc->lm_data->len); + + if (sc->lm_xfer_direction == DIRECTION_WRITE) { + lpc_dmac_disable_channel(sc->lm_dev, LPC_MMC_DMACH_WRITE); + lpc_mmc_write_4(sc, LPC_SD_DATACTRL, 0); + } + + sc->lm_req->done(sc->lm_req); + sc->lm_req = NULL; + lpc_mmc_write_4(sc, LPC_SD_CLEAR, LPC_SD_STATUS_DATABLOCKEND); + } + + debugf("done\n"); +} + +static int +lpc_mmc_request(device_t bus, device_t child, struct mmc_request *req) +{ + struct lpc_mmc_softc *sc = device_get_softc(bus); + + debugf("request: %p\n", req); + + lpc_mmc_lock(sc); + if (sc->lm_req) + return (EBUSY); + + sc->lm_req = req; + + if (req->cmd->data && req->cmd->data->flags & MMC_DATA_WRITE) { + memcpy(sc->lm_buffer, req->cmd->data->data, req->cmd->data->len); + lpc_mmc_cmd(sc, req->cmd); + lpc_mmc_unlock(sc); + return (0); + } + + if (req->cmd->data) + lpc_mmc_setup_xfer(sc, req->cmd->data); + + lpc_mmc_cmd(sc, req->cmd); + lpc_mmc_unlock(sc); + + return (0); +} + +static void +lpc_mmc_cmd(struct lpc_mmc_softc *sc, struct mmc_command *cmd) +{ + uint32_t cmdreg = 0; + + debugf("cmd: %d arg: 0x%08x\n", cmd->opcode, cmd->arg); + + if (lpc_mmc_read_4(sc, LPC_SD_COMMAND) & LPC_SD_COMMAND_ENABLE) { + lpc_mmc_write_4(sc, LPC_SD_COMMAND, 0); + DELAY(1000); + } + + sc->lm_flags &= ~LPC_SD_FLAGS_IGNORECRC; + + if (cmd->flags & MMC_RSP_PRESENT) + cmdreg |= LPC_SD_COMMAND_RESPONSE; + + if (MMC_RSP(cmd->flags) == MMC_RSP_R2) + cmdreg |= LPC_SD_COMMAND_LONGRSP; + + if (MMC_RSP(cmd->flags) == MMC_RSP_R3) + sc->lm_flags |= LPC_SD_FLAGS_IGNORECRC; + + cmdreg |= LPC_SD_COMMAND_ENABLE; + cmdreg |= (cmd->opcode & LPC_SD_COMMAND_CMDINDEXMASK); + + lpc_mmc_write_4(sc, LPC_SD_MASK0, 0xffffffff); + lpc_mmc_write_4(sc, LPC_SD_MASK1, 0xffffffff); + lpc_mmc_write_4(sc, LPC_SD_ARGUMENT, cmd->arg); + lpc_mmc_write_4(sc, LPC_SD_COMMAND, cmdreg); +} + +static void +lpc_mmc_setup_xfer(struct lpc_mmc_softc *sc, struct mmc_data *data) +{ + uint32_t datactrl = 0; + int data_words = data->len / 4; + + sc->lm_data = data; + sc->lm_xfer_done = 0; + + debugf("data: %p, len: %d, %s\n", data, + data->len, (data->flags & MMC_DATA_READ) ? "read" : "write"); + + if (data->flags & MMC_DATA_READ) { + sc->lm_xfer_direction = DIRECTION_READ; + lpc_dmac_setup_transfer(sc->lm_dev, LPC_MMC_DMACH_READ, + LPC_SD_BASE + LPC_SD_FIFO, sc->lm_buffer_phys, + data_words, 0); + } + + if (data->flags & MMC_DATA_WRITE) { + sc->lm_xfer_direction = DIRECTION_WRITE; + lpc_dmac_setup_transfer(sc->lm_dev, LPC_MMC_DMACH_WRITE, + sc->lm_buffer_phys, LPC_SD_BASE + LPC_SD_FIFO, + data_words, 0); + } + + datactrl |= (sc->lm_xfer_direction + ? LPC_SD_DATACTRL_WRITE + : LPC_SD_DATACTRL_READ); + + datactrl |= LPC_SD_DATACTRL_DMAENABLE | LPC_SD_DATACTRL_ENABLE; + datactrl |= (ffs(data->len) - 1) << 4; + + debugf("datactrl: 0x%08x\n", datactrl); + + lpc_mmc_write_4(sc, LPC_SD_DATATIMER, 0xFFFF0000); + lpc_mmc_write_4(sc, LPC_SD_DATALENGTH, data->len); + lpc_mmc_write_4(sc, LPC_SD_DATACTRL, datactrl); +} + +static int +lpc_mmc_read_ivar(device_t bus, device_t child, int which, + uintptr_t *result) +{ + struct lpc_mmc_softc *sc = device_get_softc(bus); + + switch (which) { + default: + return (EINVAL); + case MMCBR_IVAR_BUS_MODE: + *(int *)result = sc->lm_host.ios.bus_mode; + break; + case MMCBR_IVAR_BUS_WIDTH: + *(int *)result = sc->lm_host.ios.bus_width; + break; + case MMCBR_IVAR_CHIP_SELECT: + *(int *)result = sc->lm_host.ios.chip_select; + break; + case MMCBR_IVAR_CLOCK: + *(int *)result = sc->lm_host.ios.clock; + break; + case MMCBR_IVAR_F_MIN: + *(int *)result = sc->lm_host.f_min; + break; + case MMCBR_IVAR_F_MAX: + *(int *)result = sc->lm_host.f_max; + break; + case MMCBR_IVAR_HOST_OCR: + *(int *)result = sc->lm_host.host_ocr; + break; + case MMCBR_IVAR_MODE: + *(int *)result = sc->lm_host.mode; + break; + case MMCBR_IVAR_OCR: + *(int *)result = sc->lm_host.ocr; + break; + case MMCBR_IVAR_POWER_MODE: + *(int *)result = sc->lm_host.ios.power_mode; + break; + case MMCBR_IVAR_VDD: + *(int *)result = sc->lm_host.ios.vdd; + break; + case MMCBR_IVAR_CAPS: + *(int *)result = sc->lm_host.caps; + break; + case MMCBR_IVAR_MAX_DATA: + *(int *)result = 1; + break; + } + + return (0); +} + +static int +lpc_mmc_write_ivar(device_t bus, device_t child, int which, + uintptr_t value) +{ + struct lpc_mmc_softc *sc = device_get_softc(bus); + + switch (which) { + default: + return (EINVAL); + case MMCBR_IVAR_BUS_MODE: + sc->lm_host.ios.bus_mode = value; + break; + case MMCBR_IVAR_BUS_WIDTH: + sc->lm_host.ios.bus_width = value; + break; + case MMCBR_IVAR_CHIP_SELECT: + sc->lm_host.ios.chip_select = value; + break; + case MMCBR_IVAR_CLOCK: + sc->lm_host.ios.clock = value; + break; + case MMCBR_IVAR_MODE: + sc->lm_host.mode = value; + break; + case MMCBR_IVAR_OCR: + sc->lm_host.ocr = value; + break; + case MMCBR_IVAR_POWER_MODE: + sc->lm_host.ios.power_mode = value; + break; + case MMCBR_IVAR_VDD: + sc->lm_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 +lpc_mmc_update_ios(device_t bus, device_t child) +{ + struct lpc_mmc_softc *sc = device_get_softc(bus); + struct mmc_ios *ios = &sc->lm_host.ios; + uint32_t clkdiv = 0, pwr = 0; + + if (ios->bus_width == bus_width_4) + clkdiv |= LPC_SD_CLOCK_WIDEBUS; + + /* Calculate clock divider */ + clkdiv = (LPC_SD_CLK / (2 * ios->clock)) - 1; + + /* Clock rate should not exceed rate requested in ios */ + if ((LPC_SD_CLK / (2 * (clkdiv + 1))) > ios->clock) + clkdiv++; + + debugf("clock: %dHz, clkdiv: %d\n", ios->clock, clkdiv); + + if (ios->bus_width == bus_width_4) { + debugf("using wide bus mode\n"); + clkdiv |= LPC_SD_CLOCK_WIDEBUS; + } + + lpc_mmc_write_4(sc, LPC_SD_CLOCK, clkdiv | LPC_SD_CLOCK_ENABLE); + + switch (ios->power_mode) { + case power_off: + pwr |= LPC_SD_POWER_CTRL_OFF; + break; + case power_up: + pwr |= LPC_SD_POWER_CTRL_UP; + break; + case power_on: + pwr |= LPC_SD_POWER_CTRL_ON; + break; + } + + if (ios->bus_mode == opendrain) + pwr |= LPC_SD_POWER_OPENDRAIN; + + lpc_mmc_write_4(sc, LPC_SD_POWER, pwr); + + return (0); +} + +static int +lpc_mmc_get_ro(device_t bus, device_t child) +{ + + return (0); +} + +static int +lpc_mmc_acquire_host(device_t bus, device_t child) +{ + struct lpc_mmc_softc *sc = device_get_softc(bus); + int error = 0; + + lpc_mmc_lock(sc); + while (sc->lm_bus_busy) + error = mtx_sleep(sc, &sc->lm_mtx, PZERO, "mmcah", 0); + + sc->lm_bus_busy++; + lpc_mmc_unlock(sc); + return (error); +} + +static int +lpc_mmc_release_host(device_t bus, device_t child) +{ + struct lpc_mmc_softc *sc = device_get_softc(bus); + + lpc_mmc_lock(sc); + sc->lm_bus_busy--; + wakeup(sc); + lpc_mmc_unlock(sc); + return (0); +} + +static void lpc_mmc_dma_rxfinish(void *arg) +{ +} + +static void lpc_mmc_dma_rxerror(void *arg) +{ + struct lpc_mmc_softc *sc = (struct lpc_mmc_softc *)arg; + device_printf(sc->lm_dev, "DMA RX error\n"); +} + +static void lpc_mmc_dma_txfinish(void *arg) +{ +} + +static void lpc_mmc_dma_txerror(void *arg) +{ + struct lpc_mmc_softc *sc = (struct lpc_mmc_softc *)arg; + device_printf(sc->lm_dev, "DMA TX error\n"); +} + +static void +lpc_mmc_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int err) +{ + struct lpc_mmc_dmamap_arg *ctx; + + if (err) + return; + + ctx = (struct lpc_mmc_dmamap_arg *)arg; + ctx->lm_dma_busaddr = segs[0].ds_addr; +} + +static device_method_t lpc_mmc_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, lpc_mmc_probe), + DEVMETHOD(device_attach, lpc_mmc_attach), + DEVMETHOD(device_detach, lpc_mmc_detach), + + /* Bus interface */ + DEVMETHOD(bus_read_ivar, lpc_mmc_read_ivar), + DEVMETHOD(bus_write_ivar, lpc_mmc_write_ivar), + DEVMETHOD(bus_print_child, bus_generic_print_child), + + /* MMC bridge interface */ + DEVMETHOD(mmcbr_update_ios, lpc_mmc_update_ios), + DEVMETHOD(mmcbr_request, lpc_mmc_request), + DEVMETHOD(mmcbr_get_ro, lpc_mmc_get_ro), + DEVMETHOD(mmcbr_acquire_host, lpc_mmc_acquire_host), + DEVMETHOD(mmcbr_release_host, lpc_mmc_release_host), + + { 0, 0 } +}; + +static devclass_t lpc_mmc_devclass; + +static driver_t lpc_mmc_driver = { + "lpcmmc", + lpc_mmc_methods, + sizeof(struct lpc_mmc_softc), +}; + +DRIVER_MODULE(lpcmmc, simplebus, lpc_mmc_driver, lpc_mmc_devclass, 0, 0); |