summaryrefslogtreecommitdiffstats
path: root/sys/dev/mmc/mmc.c
diff options
context:
space:
mode:
authorimp <imp@FreeBSD.org>2006-10-20 06:39:59 +0000
committerimp <imp@FreeBSD.org>2006-10-20 06:39:59 +0000
commit30743982cadbd960ddcea071a6305c7b9a367999 (patch)
tree96c818b8826a8a880bef1872dab1f4baf86767e8 /sys/dev/mmc/mmc.c
parent731fe330c9cacb0edaa9afb6f2f7ba70ed1bdbfe (diff)
downloadFreeBSD-src-30743982cadbd960ddcea071a6305c7b9a367999.zip
FreeBSD-src-30743982cadbd960ddcea071a6305c7b9a367999.tar.gz
Preliminary MMC stack. This stack supports SD 1.0 cards only, but
should be easily adapted to SD 2.0 (aka SDHC), SDIO, MMC and MMCplus cards. At the present time, there's only one bridge driver for the ARM9 based Atmel AT91RM9200.
Diffstat (limited to 'sys/dev/mmc/mmc.c')
-rw-r--r--sys/dev/mmc/mmc.c745
1 files changed, 745 insertions, 0 deletions
diff --git a/sys/dev/mmc/mmc.c b/sys/dev/mmc/mmc.c
new file mode 100644
index 0000000..83c13c3
--- /dev/null
+++ b/sys/dev/mmc/mmc.c
@@ -0,0 +1,745 @@
+/*-
+ * Copyright (c) 2006 Bernd Walter. All rights reserved.
+ * Copyright (c) 2006 M. Warner Losh. 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 ``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 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/kernel.h>
+#include <sys/malloc.h>
+#include <sys/lock.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/bus.h>
+
+#include <dev/mmc/mmcreg.h>
+#include <dev/mmc/mmcbrvar.h>
+#include <dev/mmc/mmcvar.h>
+#include "mmcbr_if.h"
+#include "mmcbus_if.h"
+
+struct mmc_softc {
+ device_t dev;
+ struct mtx sc_mtx;
+ struct intr_config_hook config_intrhook;
+ device_t owner;
+ uint32_t last_rca;
+};
+
+/*
+ * Per-card data
+ */
+struct mmc_ivars {
+ uint32_t raw_cid[4]; /* Raw bits of the CID */
+ uint32_t raw_csd[4]; /* Raw bits of the CSD */
+ uint16_t rca;
+ enum mmc_card_mode mode;
+ struct mmc_cid cid; /* cid decoded */
+ struct mmc_csd csd; /* csd decoded */
+};
+
+#define CMD_RETRIES 3
+
+/* bus entry points */
+static int mmc_probe(device_t dev);
+static int mmc_attach(device_t dev);
+static int mmc_detach(device_t dev);
+
+#define MMC_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)
+#define MMC_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)
+#define MMC_LOCK_INIT(_sc) \
+ mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \
+ "mmc", MTX_DEF)
+#define MMC_LOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx);
+#define MMC_ASSERT_LOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_OWNED);
+#define MMC_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED);
+
+static void mmc_delayed_attach(void *);
+static int mmc_wait_for_cmd(struct mmc_softc *sc, struct mmc_command *cmd,
+ int retries);
+static int mmc_wait_for_command(struct mmc_softc *sc, uint32_t opcode,
+ uint32_t arg, uint32_t flags, uint32_t *resp, int retries);
+
+static void
+mmc_ms_delay(int ms)
+{
+ DELAY(1000 * ms); /* XXX BAD */
+}
+
+static int
+mmc_probe(device_t dev)
+{
+
+ device_set_desc(dev, "mmc/sd bus");
+ return (0);
+}
+
+static int
+mmc_attach(device_t dev)
+{
+ struct mmc_softc *sc;
+
+ sc = device_get_softc(dev);
+ sc->dev = dev;
+ MMC_LOCK_INIT(sc);
+
+ /* We'll probe and attach our children later, but before / mount */
+ sc->config_intrhook.ich_func = mmc_delayed_attach;
+ sc->config_intrhook.ich_arg = sc;
+ if (config_intrhook_establish(&sc->config_intrhook) != 0)
+ device_printf(dev, "config_intrhook_establish failed\n");
+ return (0);
+}
+
+static int
+mmc_detach(device_t dev)
+{
+ return (EBUSY); /* XXX */
+}
+
+static int
+mmc_acquire_bus(device_t busdev, device_t dev)
+{
+ struct mmc_softc *sc;
+ int err;
+ int rca;
+
+ err = MMCBR_ACQUIRE_HOST(device_get_parent(busdev), dev);
+ if (err)
+ return (err);
+ sc = device_get_softc(busdev);
+ MMC_LOCK(sc);
+ if (sc->owner)
+ panic("mmc: host bridge didn't seralize us.");
+ sc->owner = dev;
+ MMC_UNLOCK(sc);
+
+ if (busdev != dev) {
+ // Keep track of the last rca that we've selected. If
+ // we're asked to do it again, don't. We never unselect
+ // unless the bus code itself wants the mmc bus.
+ rca = mmc_get_rca(dev);
+ if (sc->last_rca != rca) {
+ mmc_wait_for_command(sc, MMC_SELECT_CARD, rca << 16,
+ MMC_RSP_R1 | MMC_CMD_AC, NULL, CMD_RETRIES);
+ sc->last_rca = rca;
+ }
+ // XXX should set bus width here?
+ } else {
+ // If there's a card selected, stand down.
+ if (sc->last_rca != 0) {
+ mmc_wait_for_command(sc, MMC_SELECT_CARD, 0,
+ MMC_RSP_R1 | MMC_CMD_AC, NULL, CMD_RETRIES);
+ sc->last_rca = 0;
+ }
+ // XXX should set bus width here?
+ }
+
+ return (0);
+}
+
+static int
+mmc_release_bus(device_t busdev, device_t dev)
+{
+ struct mmc_softc *sc;
+ int err;
+
+ sc = device_get_softc(busdev);
+
+ MMC_LOCK(sc);
+ if (!sc->owner)
+ panic("mmc: releasing unowned bus.");
+ if (sc->owner != dev)
+ panic("mmc: you don't own the bus. game over.");
+ MMC_UNLOCK(sc);
+ err = MMCBR_RELEASE_HOST(device_get_parent(busdev), dev);
+ if (err)
+ return (err);
+ MMC_LOCK(sc);
+ sc->owner = NULL;
+ MMC_UNLOCK(sc);
+ return (0);
+}
+
+static void
+mmc_rescan_cards(struct mmc_softc *sc)
+{
+ /* XXX: Look at the children and see if they respond to status */
+}
+
+static uint32_t
+mmc_select_vdd(struct mmc_softc *sc, uint32_t ocr)
+{
+ // XXX
+ return ocr;
+}
+
+static int
+mmc_highest_voltage(uint32_t ocr)
+{
+ int i;
+
+ for (i = 30; i >= 0; i--)
+ if (ocr & (1 << i))
+ return i;
+ return (-1);
+}
+
+static void
+mmc_wakeup(struct mmc_request *req)
+{
+ struct mmc_softc *sc;
+
+// printf("Wakeup for req %p done_data %p\n", req, req->done_data);
+ sc = (struct mmc_softc *)req->done_data;
+ MMC_LOCK(sc);
+ req->flags |= MMC_REQ_DONE;
+ wakeup(req);
+ MMC_UNLOCK(sc);
+}
+
+static int
+mmc_wait_for_req(struct mmc_softc *sc, struct mmc_request *req)
+{
+ int err;
+
+ req->done = mmc_wakeup;
+ req->done_data = sc;
+// printf("Submitting request %p sc %p\n", req, sc);
+ MMCBR_REQUEST(device_get_parent(sc->dev), sc->dev, req);
+ MMC_LOCK(sc);
+ do {
+ err = msleep(req, &sc->sc_mtx, PZERO | PCATCH, "mmcreq",
+ hz / 10);
+ } while (!(req->flags & MMC_REQ_DONE) && err == EAGAIN);
+// printf("Request %p done with error %d\n", req, err);
+ MMC_UNLOCK(sc);
+ return (err);
+}
+
+static int
+mmc_wait_for_request(device_t brdev, device_t reqdev, struct mmc_request *req)
+{
+ struct mmc_softc *sc = device_get_softc(brdev);
+
+ return mmc_wait_for_req(sc, req);
+}
+
+static int
+mmc_wait_for_cmd(struct mmc_softc *sc, struct mmc_command *cmd, int retries)
+{
+ struct mmc_request mreq;
+
+ memset(&mreq, 0, sizeof(mreq));
+ memset(cmd->resp, 0, sizeof(cmd->resp));
+ cmd->retries = retries;
+ cmd->data = NULL;
+ mreq.cmd = cmd;
+// printf("CMD: %x ARG %x\n", cmd->opcode, cmd->arg);
+ mmc_wait_for_req(sc, &mreq);
+ return (cmd->error);
+}
+
+static int
+mmc_wait_for_app_cmd(struct mmc_softc *sc, uint32_t rca,
+ struct mmc_command *cmd, int retries)
+{
+ struct mmc_command appcmd;
+ int err = MMC_ERR_NONE, i;
+
+ for (i = 0; i <= retries; i++) {
+ appcmd.opcode = MMC_APP_CMD;
+ appcmd.arg = rca << 16;
+ appcmd.flags = MMC_RSP_R1 | MMC_CMD_AC;
+ mmc_wait_for_cmd(sc, &appcmd, 0);
+ err = appcmd.error;
+ if (err != MMC_ERR_NONE)
+ continue;
+ if (!(appcmd.resp[0] & R1_APP_CMD))
+ return MMC_ERR_FAILED;
+ mmc_wait_for_cmd(sc, cmd, 0);
+ err = cmd->error;
+ if (err == MMC_ERR_NONE)
+ break;
+ }
+ return (err);
+}
+
+static int
+mmc_wait_for_command(struct mmc_softc *sc, uint32_t opcode,
+ uint32_t arg, uint32_t flags, uint32_t *resp, int retries)
+{
+ struct mmc_command cmd;
+ int err;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.opcode = opcode;
+ cmd.arg = arg;
+ cmd.flags = flags;
+ err = mmc_wait_for_cmd(sc, &cmd, retries);
+ if (err)
+ return (err);
+ if (cmd.error)
+ return (cmd.error);
+ if (resp) {
+ if (flags & MMC_RSP_136)
+ memcpy(resp, cmd.resp, 4 * sizeof(uint32_t));
+ else
+ *resp = cmd.resp[0];
+ }
+ return (0);
+}
+
+static void
+mmc_idle_cards(struct mmc_softc *sc)
+{
+ device_t dev;
+ struct mmc_command cmd;
+
+ dev = sc->dev;
+ mmcbr_set_chip_select(dev, cs_high);
+ mmcbr_update_ios(dev);
+ mmc_ms_delay(1);
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.opcode = MMC_GO_IDLE_STATE;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_NONE | MMC_CMD_BC;
+ mmc_wait_for_cmd(sc, &cmd, 0);
+ mmc_ms_delay(1);
+
+ mmcbr_set_chip_select(dev, cs_dontcare);
+ mmcbr_update_ios(dev);
+ mmc_ms_delay(1);
+}
+
+static int
+mmc_send_app_op_cond(struct mmc_softc *sc, uint32_t ocr, uint32_t *rocr)
+{
+ struct mmc_command cmd;
+ int err = MMC_ERR_NONE, i;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.opcode = ACMD_SD_SEND_OP_COND;
+ cmd.arg = ocr;
+ cmd.flags = MMC_RSP_R3 | MMC_CMD_BCR;
+
+ for (i = 0; i < 10; i++) {
+ err = mmc_wait_for_app_cmd(sc, 0, &cmd, CMD_RETRIES);
+ if (err != MMC_ERR_NONE)
+ break;
+ if ((cmd.resp[0] & MMC_OCR_CARD_BUSY) || ocr == 0)
+ break;
+ err = MMC_ERR_TIMEOUT;
+ mmc_ms_delay(10);
+ }
+ if (rocr && err == MMC_ERR_NONE)
+ *rocr = cmd.resp[0];
+ return err;
+}
+
+static int
+mmc_send_op_cond(struct mmc_softc *sc, uint32_t ocr, uint32_t *rocr)
+{
+ struct mmc_command cmd;
+ int err = MMC_ERR_NONE, i;
+
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.opcode = MMC_SEND_OP_COND;
+ cmd.arg = ocr;
+ cmd.flags = MMC_RSP_R3 | MMC_CMD_BCR;
+
+ for (i = 0; i < 100; i++) {
+ err = mmc_wait_for_cmd(sc, &cmd, CMD_RETRIES);
+ if (err != MMC_ERR_NONE)
+ break;
+ if ((cmd.resp[0] & MMC_OCR_CARD_BUSY) || ocr == 0)
+ break;
+ err = MMC_ERR_TIMEOUT;
+ mmc_ms_delay(10);
+ }
+ if (rocr && err == MMC_ERR_NONE)
+ *rocr = cmd.resp[0];
+ return err;
+}
+
+static void
+mmc_power_up(struct mmc_softc *sc)
+{
+ device_t dev;
+
+ dev = sc->dev;
+ mmcbr_set_vdd(dev, mmc_highest_voltage(mmcbr_get_host_ocr(dev)));
+ mmcbr_set_bus_mode(dev, opendrain);
+ mmcbr_set_chip_select(dev, cs_dontcare);
+ mmcbr_set_bus_width(dev, bus_width_1);
+ mmcbr_set_power_mode(dev, power_up);
+ mmcbr_set_clock(dev, 0);
+ mmcbr_update_ios(dev);
+ mmc_ms_delay(1);
+
+ mmcbr_set_clock(dev, mmcbr_get_f_min(sc->dev));
+ mmcbr_set_power_mode(dev, power_on);
+ mmcbr_update_ios(dev);
+ mmc_ms_delay(2);
+}
+
+// I wonder if the following is endian safe.
+static uint32_t
+mmc_get_bits(uint32_t *bits, int start, int size)
+{
+ const int i = 3 - (start / 32);
+ const int shift = start & 31;
+ uint32_t retval = bits[i] >> shift;
+ if (size + shift > 32)
+ retval |= bits[i - 1] << (32 - shift);
+ return retval & ((1 << size) - 1);
+}
+
+static void
+mmc_decode_cid(int is_sd, uint32_t *raw_cid, struct mmc_cid *cid)
+{
+ int i;
+
+ memset(cid, 0, sizeof(*cid));
+ if (is_sd) {
+ /* There's no version info, so we take it on faith */
+ cid->mid = mmc_get_bits(raw_cid, 120, 8);
+ cid->oid = mmc_get_bits(raw_cid, 104, 16);
+ for (i = 0; i < 5; i++)
+ cid->pnm[i] = mmc_get_bits(raw_cid, 96 - i * 8, 8);
+ cid->prv = mmc_get_bits(raw_cid, 56, 8);
+ cid->psn = mmc_get_bits(raw_cid, 24, 32);
+ cid->mdt_year = mmc_get_bits(raw_cid, 12, 8) + 2001;
+ cid->mdt_month = mmc_get_bits(raw_cid, 8, 4);
+ } else {
+ // XXX write me
+ panic("write mmc cid decoder");
+ }
+}
+
+static const int exp[8] = {
+ 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000
+};
+static const int mant[16] = {
+ 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80
+};
+static const int cur_min[8] = {
+ 500, 1000, 5000, 10000, 25000, 35000, 60000, 100000
+};
+static const int cur_max[8] = {
+ 1000, 5000, 10000, 25000, 35000, 45000, 800000, 200000
+};
+
+static void
+mmc_decode_csd(int is_sd, uint32_t *raw_csd, struct mmc_csd *csd)
+{
+ int v;
+ int m;
+ int e;
+
+ memset(csd, 0, sizeof(*csd));
+ if (is_sd) {
+ csd->csd_structure = v = mmc_get_bits(raw_csd, 126, 2);
+ if (v == 0) {
+ m = mmc_get_bits(raw_csd, 115, 4);
+ e = mmc_get_bits(raw_csd, 112, 3);
+ csd->tacc = exp[e] * mant[m] + 9 / 10;
+ csd->nsac = mmc_get_bits(raw_csd, 104, 8) * 100;
+ m = mmc_get_bits(raw_csd, 99, 4);
+ e = mmc_get_bits(raw_csd, 96, 3);
+ csd->tran_speed = exp[e] * 10000 * mant[m];
+ csd->ccc = mmc_get_bits(raw_csd, 84, 12);
+ csd->read_bl_len = 1 << mmc_get_bits(raw_csd, 80, 4);
+ csd->read_bl_partial = mmc_get_bits(raw_csd, 79, 1);
+ csd->write_blk_misalign = mmc_get_bits(raw_csd, 78, 1);
+ csd->read_blk_misalign = mmc_get_bits(raw_csd, 77, 1);
+ csd->dsr_imp = mmc_get_bits(raw_csd, 76, 1);
+ csd->vdd_r_curr_min = cur_min[mmc_get_bits(raw_csd, 59, 3)];
+ csd->vdd_r_curr_max = cur_max[mmc_get_bits(raw_csd, 56, 3)];
+ csd->vdd_w_curr_min = cur_min[mmc_get_bits(raw_csd, 53, 3)];
+ csd->vdd_w_curr_max = cur_max[mmc_get_bits(raw_csd, 50, 3)];
+ m = mmc_get_bits(raw_csd, 62, 12);
+ e = mmc_get_bits(raw_csd, 47, 3);
+ csd->capacity = ((1 + m) << (e + 2)) * csd->read_bl_len;
+ csd->erase_blk_en = mmc_get_bits(raw_csd, 46, 1);
+ csd->sector_size = mmc_get_bits(raw_csd, 39, 7);
+ csd->wp_grp_size = mmc_get_bits(raw_csd, 32, 7);
+ csd->wp_grp_enable = mmc_get_bits(raw_csd, 31, 1);
+ csd->r2w_factor = 1 << mmc_get_bits(raw_csd, 26, 3);
+ csd->write_bl_len = 1 << mmc_get_bits(raw_csd, 22, 4);
+ csd->write_bl_partial = mmc_get_bits(raw_csd, 21, 1);
+ } else if (v == 1) {
+ panic("Write SDHC CSD parser");
+ } else
+ panic("unknown SD CSD version");
+ } else {
+ panic("Write a MMC CSD parser");
+ }
+}
+
+static int
+mmc_all_send_cid(struct mmc_softc *sc, uint32_t *rawcid)
+{
+ struct mmc_command cmd;
+ int err;
+
+ cmd.opcode = MMC_ALL_SEND_CID;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_R2 | MMC_CMD_BCR;
+ err = mmc_wait_for_cmd(sc, &cmd, 0);
+ memcpy(rawcid, cmd.resp, 4 * sizeof(uint32_t));
+ return (err);
+}
+
+static int
+mmc_send_csd(struct mmc_softc *sc, uint16_t rca, uint32_t *rawcid)
+{
+ struct mmc_command cmd;
+ int err;
+
+ cmd.opcode = MMC_SEND_CSD;
+ cmd.arg = rca << 16;
+ cmd.flags = MMC_RSP_R2 | MMC_CMD_BCR;
+ err = mmc_wait_for_cmd(sc, &cmd, 0);
+ memcpy(rawcid, cmd.resp, 4 * sizeof(uint32_t));
+ return (err);
+}
+
+static int
+mmc_send_relative_addr(struct mmc_softc *sc, uint32_t *resp)
+{
+ struct mmc_command cmd;
+ int err;
+
+ cmd.opcode = SD_SEND_RELATIVE_ADDR;
+ cmd.arg = 0;
+ cmd.flags = MMC_RSP_R6 | MMC_CMD_BCR;
+ err = mmc_wait_for_cmd(sc, &cmd, 0);
+ *resp = cmd.resp[0];
+ return (err);
+}
+
+static void
+mmc_discover_cards(struct mmc_softc *sc)
+{
+ struct mmc_ivars *ivar;
+ int err;
+ uint32_t resp;
+ device_t child;
+
+ while (1) {
+ ivar = malloc(sizeof(struct mmc_ivars), M_DEVBUF, M_WAITOK);
+ err = mmc_all_send_cid(sc, ivar->raw_cid);
+ if (err == MMC_ERR_TIMEOUT)
+ break;
+ if (err != MMC_ERR_NONE) {
+ printf("Error reading CID %d\n", err);
+ break;
+ }
+ if (mmcbr_get_mode(sc->dev) == mode_sd) {
+ ivar->mode = mode_sd;
+ mmc_decode_cid(1, ivar->raw_cid, &ivar->cid);
+ mmc_send_relative_addr(sc, &resp);
+ ivar->rca = resp >> 16;
+ // RO check
+ mmc_send_csd(sc, ivar->rca, ivar->raw_csd);
+ mmc_decode_csd(1, ivar->raw_csd, &ivar->csd);
+ printf("SD CARD: %lld bytes\n", ivar->csd.capacity);
+ child = device_add_child(sc->dev, NULL, -1);
+ device_set_ivars(child, ivar);
+ break;
+ }
+ panic("Write MMC card code here");
+ }
+}
+
+static void
+mmc_go_discovery(struct mmc_softc *sc)
+{
+ uint32_t ocr;
+ device_t dev;
+
+ dev = sc->dev;
+ if (mmcbr_get_power_mode(dev) != power_on) {
+ // First, try SD modes
+ mmcbr_set_mode(dev, mode_sd);
+ mmc_power_up(sc);
+ mmcbr_set_bus_mode(dev, pushpull);
+ mmc_idle_cards(sc);
+ if (mmc_send_app_op_cond(sc, 0, &ocr) != MMC_ERR_NONE) {
+ // Failed, try MMC
+ mmcbr_set_mode(dev, mode_mmc);
+ if (mmc_send_op_cond(sc, 0, &ocr) != MMC_ERR_NONE)
+ return; // Failed both, punt! XXX power down?
+ }
+ mmcbr_set_ocr(dev, mmc_select_vdd(sc, ocr));
+ if (mmcbr_get_ocr(dev) != 0)
+ mmc_idle_cards(sc);
+ } else {
+ mmcbr_set_bus_mode(dev, opendrain);
+ mmcbr_set_clock(dev, mmcbr_get_f_min(dev));
+ mmcbr_update_ios(dev);
+ // XXX recompute vdd based on new cards?
+ }
+ /*
+ * Make sure that we have a mutually agreeable voltage to at least
+ * one card on the bus.
+ */
+ if (mmcbr_get_ocr(dev) == 0)
+ return;
+ /*
+ * Reselect the cards after we've idled them above.
+ */
+ if (mmcbr_get_mode(dev) == mode_sd)
+ mmc_send_app_op_cond(sc, mmcbr_get_ocr(dev), NULL);
+ else
+ mmc_send_op_cond(sc, mmcbr_get_ocr(dev), NULL);
+ mmc_discover_cards(sc);
+
+ mmcbr_set_bus_mode(dev, pushpull);
+ mmcbr_update_ios(dev);
+ bus_generic_attach(dev);
+// mmc_update_children_sysctl(dev);
+}
+
+static int
+mmc_calculate_clock(struct mmc_softc *sc)
+{
+ int max_dtr = 0;
+ int nkid, i, f_min, f_max;
+ device_t *kids;
+
+ f_min = mmcbr_get_f_min(sc->dev);
+ f_max = mmcbr_get_f_max(sc->dev);
+ max_dtr = f_max;
+ if (device_get_children(sc->dev, &kids, &nkid) != 0)
+ panic("can't get children");
+ for (i = 0; i < nkid; i++)
+ if (mmc_get_tran_speed(kids[i]) < max_dtr)
+ max_dtr = mmc_get_tran_speed(kids[i]);
+ free(kids, M_TEMP);
+ device_printf(sc->dev, "setting transfer rate to %d.%03dMHz\n",
+ max_dtr / 1000000, (max_dtr / 1000) % 1000);
+ return max_dtr;
+}
+
+static void
+mmc_scan(struct mmc_softc *sc)
+{
+ device_t dev;
+
+ dev = sc->dev;
+ mmc_acquire_bus(dev, dev);
+
+ if (mmcbr_get_power_mode(dev) == power_on)
+ mmc_rescan_cards(sc);
+ mmc_go_discovery(sc);
+ mmcbr_set_clock(dev, mmc_calculate_clock(sc));
+ mmcbr_update_ios(dev);
+
+ mmc_release_bus(dev, dev);
+ // XXX probe/attach/detach children?
+}
+
+static int
+mmc_read_ivar(device_t bus, device_t child, int which, u_char *result)
+{
+ struct mmc_ivars *ivar = device_get_ivars(child);
+
+ switch (which) {
+ default:
+ return (EINVAL);
+ case MMC_IVAR_DSR_IMP:
+ *(int *)result = ivar->csd.dsr_imp;
+ break;
+ case MMC_IVAR_MEDIA_SIZE:
+ *(int *)result = ivar->csd.capacity;
+ break;
+ case MMC_IVAR_MODE:
+ *(int *)result = ivar->mode;
+ break;
+ case MMC_IVAR_RCA:
+ *(int *)result = ivar->rca;
+ break;
+ case MMC_IVAR_SECTOR_SIZE:
+ *(int *)result = 512;
+ break;
+ case MMC_IVAR_TRAN_SPEED:
+ *(int *)result = ivar->csd.tran_speed;
+ break;
+ }
+ return (0);
+}
+
+static int
+mmc_write_ivar(device_t bus, device_t child, int which, uintptr_t value)
+{
+ // None are writable ATM
+ switch (which) {
+ default:
+ return (EINVAL);
+ }
+ return (0);
+}
+
+
+static void
+mmc_delayed_attach(void *xsc)
+{
+ struct mmc_softc *sc = xsc;
+
+ mmc_scan(sc);
+ config_intrhook_disestablish(&sc->config_intrhook);
+}
+
+static device_method_t mmc_methods[] = {
+ /* device_if */
+ DEVMETHOD(device_probe, mmc_probe),
+ DEVMETHOD(device_attach, mmc_attach),
+ DEVMETHOD(device_detach, mmc_detach),
+
+ /* Bus interface */
+ DEVMETHOD(bus_read_ivar, mmc_read_ivar),
+ DEVMETHOD(bus_write_ivar, mmc_write_ivar),
+
+ /* MMC Bus interface */
+ DEVMETHOD(mmcbus_wait_for_request, mmc_wait_for_request),
+ DEVMETHOD(mmcbus_acquire_bus, mmc_acquire_bus),
+ DEVMETHOD(mmcbus_release_bus, mmc_release_bus),
+
+ {0, 0},
+};
+
+static driver_t mmc_driver = {
+ "mmc",
+ mmc_methods,
+ sizeof(struct mmc_softc),
+};
+static devclass_t mmc_devclass;
+
+
+DRIVER_MODULE(mmc, at91_mci, mmc_driver, mmc_devclass, 0, 0);
OpenPOWER on IntegriCloud