From 17b0429dde9ab60f9cee8e07ab28c7dc6cfe6efd Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sun, 22 Jul 2007 22:18:46 +0200 Subject: mmc: remove custom error codes Convert the MMC layer to use standard error codes and not its own, incompatible values. Signed-off-by: Pierre Ossman --- drivers/mmc/card/block.c | 4 ++-- drivers/mmc/core/core.c | 4 ++-- drivers/mmc/core/mmc.c | 36 ++++++++++++++--------------- drivers/mmc/core/mmc_ops.c | 34 +++++++++++++-------------- drivers/mmc/core/sd.c | 56 ++++++++++++++++++++++----------------------- drivers/mmc/core/sd_ops.c | 46 ++++++++++++++++++------------------- drivers/mmc/host/at91_mci.c | 12 ++++------ drivers/mmc/host/au1xmmc.c | 32 +++++++++++++------------- drivers/mmc/host/imxmmc.c | 14 ++++++------ drivers/mmc/host/mmci.c | 12 +++++----- drivers/mmc/host/omap.c | 12 +++++----- drivers/mmc/host/pxamci.c | 12 +++++----- drivers/mmc/host/sdhci.c | 51 +++++++++++++++++++---------------------- drivers/mmc/host/tifm_sd.c | 20 ++++++++-------- drivers/mmc/host/wbsd.c | 41 ++++++++++++++++----------------- 15 files changed, 190 insertions(+), 196 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 93fe2e5dd61..0da341acf32 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -154,7 +154,7 @@ static u32 mmc_sd_num_wr_blocks(struct mmc_card *card) cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; err = mmc_wait_for_cmd(card->host, &cmd, 0); - if ((err != MMC_ERR_NONE) || !(cmd.resp[0] & R1_APP_CMD)) + if (err || !(cmd.resp[0] & R1_APP_CMD)) return (u32)-1; memset(&cmd, 0, sizeof(struct mmc_command)); @@ -192,7 +192,7 @@ static u32 mmc_sd_num_wr_blocks(struct mmc_card *card) mmc_wait_for_req(card->host, &mrq); - if (cmd.error != MMC_ERR_NONE || data.error != MMC_ERR_NONE) + if (cmd.error || data.error) return (u32)-1; blocks = ntohl(blocks); diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index bfd2ae5bd66..63b67296e92 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -598,7 +598,7 @@ void mmc_rescan(struct work_struct *work) mmc_send_if_cond(host, host->ocr_avail); err = mmc_send_app_op_cond(host, 0, &ocr); - if (err == MMC_ERR_NONE) { + if (!err) { if (mmc_attach_sd(host, ocr)) mmc_power_off(host); } else { @@ -607,7 +607,7 @@ void mmc_rescan(struct work_struct *work) * searching for MMC cards. */ err = mmc_send_op_cond(host, 0, &ocr); - if (err == MMC_ERR_NONE) { + if (!err) { if (mmc_attach_mmc(host, ocr)) mmc_power_off(host); } else { diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 21d7f48e1d4..fe483d5af74 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -164,10 +164,10 @@ static int mmc_read_ext_csd(struct mmc_card *card) BUG_ON(!card); - err = MMC_ERR_FAILED; + err = -EIO; if (card->csd.mmca_vsn < CSD_SPEC_VER_4) - return MMC_ERR_NONE; + return 0; /* * As the ext_csd is so large and mostly unused, we don't store the @@ -178,11 +178,11 @@ static int mmc_read_ext_csd(struct mmc_card *card) printk(KERN_ERR "%s: could not allocate a buffer to " "receive the ext_csd. mmc v4 cards will be " "treated as v3.\n", mmc_hostname(card->host)); - return MMC_ERR_FAILED; + return -ENOMEM; } err = mmc_send_ext_csd(card, ext_csd); - if (err != MMC_ERR_NONE) { + if (err) { /* * High capacity cards should have this "magic" size * stored in their CSD. @@ -197,7 +197,7 @@ static int mmc_read_ext_csd(struct mmc_card *card) "EXT_CSD, performance might " "suffer.\n", mmc_hostname(card->host)); - err = MMC_ERR_NONE; + err = 0; } goto out; } @@ -258,14 +258,14 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, /* The extra bit indicates that we support high capacity */ err = mmc_send_op_cond(host, ocr | (1 << 30), NULL); - if (err != MMC_ERR_NONE) + if (err) goto err; /* * Fetch CID from card. */ err = mmc_all_send_cid(host, cid); - if (err != MMC_ERR_NONE) + if (err) goto err; if (oldcard) { @@ -290,7 +290,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, * Set card RCA. */ err = mmc_set_relative_addr(card); - if (err != MMC_ERR_NONE) + if (err) goto free_card; mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); @@ -300,7 +300,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, * Fetch CSD from card. */ err = mmc_send_csd(card, card->raw_csd); - if (err != MMC_ERR_NONE) + if (err) goto free_card; err = mmc_decode_csd(card); @@ -315,7 +315,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, * Select card, as all following commands rely on that. */ err = mmc_select_card(card); - if (err != MMC_ERR_NONE) + if (err) goto free_card; if (!oldcard) { @@ -323,7 +323,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, * Fetch and process extened CSD. */ err = mmc_read_ext_csd(card); - if (err != MMC_ERR_NONE) + if (err) goto free_card; } @@ -334,7 +334,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, (host->caps & MMC_CAP_MMC_HIGHSPEED)) { err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_HS_TIMING, 1); - if (err != MMC_ERR_NONE) + if (err) goto free_card; mmc_card_set_highspeed(card); @@ -363,7 +363,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, (host->caps & MMC_CAP_4_BIT_DATA)) { err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BUS_WIDTH, EXT_CSD_BUS_WIDTH_4); - if (err != MMC_ERR_NONE) + if (err) goto free_card; mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4); @@ -372,14 +372,14 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, if (!oldcard) host->card = card; - return MMC_ERR_NONE; + return 0; free_card: if (!oldcard) mmc_remove_card(card); err: - return MMC_ERR_FAILED; + return -EIO; } /* @@ -413,7 +413,7 @@ static void mmc_detect(struct mmc_host *host) mmc_release_host(host); - if (err != MMC_ERR_NONE) { + if (err) { mmc_remove(host); mmc_claim_host(host); @@ -502,7 +502,7 @@ static void mmc_resume(struct mmc_host *host) err = mmc_init_card(host, host->ocr, host->card); mmc_release_host(host); - if (err != MMC_ERR_NONE) { + if (err) { mmc_remove(host); mmc_claim_host(host); @@ -565,7 +565,7 @@ int mmc_attach_mmc(struct mmc_host *host, u32 ocr) * Detect and init the card. */ err = mmc_init_card(host, host->ocr, NULL); - if (err != MMC_ERR_NONE) + if (err) goto err; mmc_release_host(host); diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index 913e75f0084..15cd575effa 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -40,10 +40,10 @@ static int _mmc_select_card(struct mmc_host *host, struct mmc_card *card) } err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) return err; - return MMC_ERR_NONE; + return 0; } int mmc_select_card(struct mmc_card *card) @@ -99,13 +99,13 @@ int mmc_send_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr) for (i = 100; i; i--) { err = mmc_wait_for_cmd(host, &cmd, 0); - if (err != MMC_ERR_NONE) + if (err) break; if (cmd.resp[0] & MMC_CARD_BUSY || ocr == 0) break; - err = MMC_ERR_TIMEOUT; + err = -ETIMEDOUT; mmc_delay(10); } @@ -131,12 +131,12 @@ int mmc_all_send_cid(struct mmc_host *host, u32 *cid) cmd.flags = MMC_RSP_R2 | MMC_CMD_BCR; err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) return err; memcpy(cid, cmd.resp, sizeof(u32) * 4); - return MMC_ERR_NONE; + return 0; } int mmc_set_relative_addr(struct mmc_card *card) @@ -154,10 +154,10 @@ int mmc_set_relative_addr(struct mmc_card *card) cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) return err; - return MMC_ERR_NONE; + return 0; } int mmc_send_csd(struct mmc_card *card, u32 *csd) @@ -176,12 +176,12 @@ int mmc_send_csd(struct mmc_card *card, u32 *csd) cmd.flags = MMC_RSP_R2 | MMC_CMD_AC; err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) return err; memcpy(csd, cmd.resp, sizeof(u32) * 4); - return MMC_ERR_NONE; + return 0; } int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd) @@ -218,12 +218,12 @@ int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd) mmc_wait_for_req(card->host, &mrq); - if (cmd.error != MMC_ERR_NONE) + if (cmd.error) return cmd.error; - if (data.error != MMC_ERR_NONE) + if (data.error) return data.error; - return MMC_ERR_NONE; + return 0; } int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value) @@ -244,10 +244,10 @@ int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value) cmd.flags = MMC_RSP_R1B | MMC_CMD_AC; err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) return err; - return MMC_ERR_NONE; + return 0; } int mmc_send_status(struct mmc_card *card, u32 *status) @@ -265,12 +265,12 @@ int mmc_send_status(struct mmc_card *card, u32 *status) cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) return err; if (status) *status = cmd.resp[0]; - return MMC_ERR_NONE; + return 0; } diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 1edc62b1e5c..00895c99d9b 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -193,30 +193,30 @@ static int mmc_read_switch(struct mmc_card *card) u8 *status; if (card->scr.sda_vsn < SCR_SPEC_VER_1) - return MMC_ERR_NONE; + return 0; if (!(card->csd.cmdclass & CCC_SWITCH)) { printk(KERN_WARNING "%s: card lacks mandatory switch " "function, performance might suffer.\n", mmc_hostname(card->host)); - return MMC_ERR_NONE; + return 0; } - err = MMC_ERR_FAILED; + err = -EIO; status = kmalloc(64, GFP_KERNEL); if (!status) { printk(KERN_ERR "%s: could not allocate a buffer for " "switch capabilities.\n", mmc_hostname(card->host)); - return err; + return -ENOMEM; } err = mmc_sd_switch(card, 0, 0, 1, status); - if (err != MMC_ERR_NONE) { + if (err) { printk(KERN_WARNING "%s: problem reading switch " "capabilities, performance might suffer.\n", mmc_hostname(card->host)); - err = MMC_ERR_NONE; + err = 0; goto out; } @@ -238,28 +238,28 @@ static int mmc_switch_hs(struct mmc_card *card) u8 *status; if (card->scr.sda_vsn < SCR_SPEC_VER_1) - return MMC_ERR_NONE; + return 0; if (!(card->csd.cmdclass & CCC_SWITCH)) - return MMC_ERR_NONE; + return 0; if (!(card->host->caps & MMC_CAP_SD_HIGHSPEED)) - return MMC_ERR_NONE; + return 0; if (card->sw_caps.hs_max_dtr == 0) - return MMC_ERR_NONE; + return 0; - err = MMC_ERR_FAILED; + err = -EIO; status = kmalloc(64, GFP_KERNEL); if (!status) { printk(KERN_ERR "%s: could not allocate a buffer for " "switch capabilities.\n", mmc_hostname(card->host)); - return err; + return -ENOMEM; } err = mmc_sd_switch(card, 1, 0, 1, status); - if (err != MMC_ERR_NONE) + if (err) goto out; if ((status[16] & 0xF) != 1) { @@ -309,18 +309,18 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, * block-addressed SDHC cards. */ err = mmc_send_if_cond(host, ocr); - if (err == MMC_ERR_NONE) + if (!err) ocr |= 1 << 30; err = mmc_send_app_op_cond(host, ocr, NULL); - if (err != MMC_ERR_NONE) + if (err) goto err; /* * Fetch CID from card. */ err = mmc_all_send_cid(host, cid); - if (err != MMC_ERR_NONE) + if (err) goto err; if (oldcard) { @@ -344,7 +344,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, * Set card RCA. */ err = mmc_send_relative_addr(host, &card->rca); - if (err != MMC_ERR_NONE) + if (err) goto free_card; mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); @@ -354,7 +354,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, * Fetch CSD from card. */ err = mmc_send_csd(card, card->raw_csd); - if (err != MMC_ERR_NONE) + if (err) goto free_card; err = mmc_decode_csd(card); @@ -368,7 +368,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, * Select card, as all following commands rely on that. */ err = mmc_select_card(card); - if (err != MMC_ERR_NONE) + if (err) goto free_card; if (!oldcard) { @@ -376,7 +376,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, * Fetch SCR from card. */ err = mmc_app_send_scr(card, card->raw_scr); - if (err != MMC_ERR_NONE) + if (err) goto free_card; err = mmc_decode_scr(card); @@ -387,7 +387,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, * Fetch switch information from card. */ err = mmc_read_switch(card); - if (err != MMC_ERR_NONE) + if (err) goto free_card; } @@ -395,7 +395,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, * Attempt to change to high-speed (if supported) */ err = mmc_switch_hs(card); - if (err != MMC_ERR_NONE) + if (err) goto free_card; /* @@ -418,7 +418,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, if ((host->caps & MMC_CAP_4_BIT_DATA) && (card->scr.bus_widths & SD_SCR_BUS_WIDTH_4)) { err = mmc_app_set_bus_width(card, MMC_BUS_WIDTH_4); - if (err != MMC_ERR_NONE) + if (err) goto free_card; mmc_set_bus_width(host, MMC_BUS_WIDTH_4); @@ -442,14 +442,14 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, if (!oldcard) host->card = card; - return MMC_ERR_NONE; + return 0; free_card: if (!oldcard) mmc_remove_card(card); err: - return MMC_ERR_FAILED; + return -EIO; } /* @@ -483,7 +483,7 @@ static void mmc_sd_detect(struct mmc_host *host) mmc_release_host(host); - if (err != MMC_ERR_NONE) { + if (err) { mmc_sd_remove(host); mmc_claim_host(host); @@ -574,7 +574,7 @@ static void mmc_sd_resume(struct mmc_host *host) err = mmc_sd_init_card(host, host->ocr, host->card); mmc_release_host(host); - if (err != MMC_ERR_NONE) { + if (err) { mmc_sd_remove(host); mmc_claim_host(host); @@ -644,7 +644,7 @@ int mmc_attach_sd(struct mmc_host *host, u32 ocr) * Detect and init the card. */ err = mmc_sd_init_card(host, host->ocr, NULL); - if (err != MMC_ERR_NONE) + if (err) goto err; mmc_release_host(host); diff --git a/drivers/mmc/core/sd_ops.c b/drivers/mmc/core/sd_ops.c index 342f340ebc2..b4d43bd0fed 100644 --- a/drivers/mmc/core/sd_ops.c +++ b/drivers/mmc/core/sd_ops.c @@ -40,14 +40,14 @@ static int mmc_app_cmd(struct mmc_host *host, struct mmc_card *card) } err = mmc_wait_for_cmd(host, &cmd, 0); - if (err != MMC_ERR_NONE) + if (err) return err; /* Check that card supported application commands */ if (!(cmd.resp[0] & R1_APP_CMD)) - return MMC_ERR_FAILED; + return -EOPNOTSUPP; - return MMC_ERR_NONE; + return 0; } /** @@ -73,7 +73,7 @@ int mmc_wait_for_app_cmd(struct mmc_host *host, struct mmc_card *card, BUG_ON(!cmd); BUG_ON(retries < 0); - err = MMC_ERR_INVALID; + err = -EIO; /* * We have to resend MMC_APP_CMD for each attempt so @@ -83,7 +83,7 @@ int mmc_wait_for_app_cmd(struct mmc_host *host, struct mmc_card *card, memset(&mrq, 0, sizeof(struct mmc_request)); err = mmc_app_cmd(host, card); - if (err != MMC_ERR_NONE) + if (err) continue; memset(&mrq, 0, sizeof(struct mmc_request)); @@ -97,7 +97,7 @@ int mmc_wait_for_app_cmd(struct mmc_host *host, struct mmc_card *card, mmc_wait_for_req(host, &mrq); err = cmd->error; - if (cmd->error == MMC_ERR_NONE) + if (!cmd->error) break; } @@ -127,14 +127,14 @@ int mmc_app_set_bus_width(struct mmc_card *card, int width) cmd.arg = SD_BUS_WIDTH_4; break; default: - return MMC_ERR_INVALID; + return -EINVAL; } err = mmc_wait_for_app_cmd(card->host, card, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) return err; - return MMC_ERR_NONE; + return 0; } int mmc_send_app_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr) @@ -152,13 +152,13 @@ int mmc_send_app_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr) for (i = 100; i; i--) { err = mmc_wait_for_app_cmd(host, NULL, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) break; if (cmd.resp[0] & MMC_CARD_BUSY || ocr == 0) break; - err = MMC_ERR_TIMEOUT; + err = -ETIMEDOUT; mmc_delay(10); } @@ -185,13 +185,13 @@ int mmc_send_if_cond(struct mmc_host *host, u32 ocr) cmd.flags = MMC_RSP_R7 | MMC_CMD_BCR; err = mmc_wait_for_cmd(host, &cmd, 0); - if (err != MMC_ERR_NONE) + if (err) return err; if ((cmd.resp[0] & 0xFF) != test_pattern) - return MMC_ERR_FAILED; + return -EIO; - return MMC_ERR_NONE; + return 0; } int mmc_send_relative_addr(struct mmc_host *host, unsigned int *rca) @@ -209,12 +209,12 @@ int mmc_send_relative_addr(struct mmc_host *host, unsigned int *rca) cmd.flags = MMC_RSP_R6 | MMC_CMD_BCR; err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) return err; *rca = cmd.resp[0] >> 16; - return MMC_ERR_NONE; + return 0; } int mmc_app_send_scr(struct mmc_card *card, u32 *scr) @@ -230,7 +230,7 @@ int mmc_app_send_scr(struct mmc_card *card, u32 *scr) BUG_ON(!scr); err = mmc_app_cmd(card->host, card); - if (err != MMC_ERR_NONE) + if (err) return err; memset(&mrq, 0, sizeof(struct mmc_request)); @@ -256,15 +256,15 @@ int mmc_app_send_scr(struct mmc_card *card, u32 *scr) mmc_wait_for_req(card->host, &mrq); - if (cmd.error != MMC_ERR_NONE) + if (cmd.error) return cmd.error; - if (data.error != MMC_ERR_NONE) + if (data.error) return data.error; scr[0] = ntohl(scr[0]); scr[1] = ntohl(scr[1]); - return MMC_ERR_NONE; + return 0; } int mmc_sd_switch(struct mmc_card *card, int mode, int group, @@ -306,11 +306,11 @@ int mmc_sd_switch(struct mmc_card *card, int mode, int group, mmc_wait_for_req(card->host, &mrq); - if (cmd.error != MMC_ERR_NONE) + if (cmd.error) return cmd.error; - if (data.error != MMC_ERR_NONE) + if (data.error) return data.error; - return MMC_ERR_NONE; + return 0; } diff --git a/drivers/mmc/host/at91_mci.c b/drivers/mmc/host/at91_mci.c index 955ea60583b..810a433ce53 100644 --- a/drivers/mmc/host/at91_mci.c +++ b/drivers/mmc/host/at91_mci.c @@ -577,24 +577,22 @@ static void at91_mci_completed_command(struct at91mci_host *host) AT91_MCI_RENDE | AT91_MCI_RTOE | AT91_MCI_DCRCE | AT91_MCI_DTOE | AT91_MCI_OVRE | AT91_MCI_UNRE)) { if ((status & AT91_MCI_RCRCE) && !(mmc_resp_type(cmd) & MMC_RSP_CRC)) { - cmd->error = MMC_ERR_NONE; + cmd->error = 0; } else { if (status & (AT91_MCI_RTOE | AT91_MCI_DTOE)) - cmd->error = MMC_ERR_TIMEOUT; + cmd->error = -ETIMEDOUT; else if (status & (AT91_MCI_RCRCE | AT91_MCI_DCRCE)) - cmd->error = MMC_ERR_BADCRC; - else if (status & (AT91_MCI_OVRE | AT91_MCI_UNRE)) - cmd->error = MMC_ERR_FIFO; + cmd->error = -EILSEQ; else - cmd->error = MMC_ERR_FAILED; + cmd->error = -EIO; pr_debug("Error detected and set to %d (cmd = %d, retries = %d)\n", cmd->error, cmd->opcode, cmd->retries); } } else - cmd->error = MMC_ERR_NONE; + cmd->error = 0; at91_mci_process_next(host); } diff --git a/drivers/mmc/host/au1xmmc.c b/drivers/mmc/host/au1xmmc.c index 34c99d4ea04..49b0367e57c 100644 --- a/drivers/mmc/host/au1xmmc.c +++ b/drivers/mmc/host/au1xmmc.c @@ -208,7 +208,7 @@ static int au1xmmc_send_command(struct au1xmmc_host *host, int wait, default: printk(KERN_INFO "au1xmmc: unhandled response type %02x\n", mmc_resp_type(cmd)); - return MMC_ERR_INVALID; + return -EINVAL; } if (flags & MMC_DATA_READ) { @@ -253,7 +253,7 @@ static int au1xmmc_send_command(struct au1xmmc_host *host, int wait, IRQ_ON(host, SD_CONFIG_CR); } - return MMC_ERR_NONE; + return 0; } static void au1xmmc_data_complete(struct au1xmmc_host *host, u32 status) @@ -278,7 +278,7 @@ static void au1xmmc_data_complete(struct au1xmmc_host *host, u32 status) while((host->flags & HOST_F_XMIT) && (status & SD_STATUS_DB)) status = au_readl(HOST_STATUS(host)); - data->error = MMC_ERR_NONE; + data->error = 0; dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, host->dma.dir); /* Process any errors */ @@ -288,14 +288,14 @@ static void au1xmmc_data_complete(struct au1xmmc_host *host, u32 status) crc |= ((status & 0x07) == 0x02) ? 0 : 1; if (crc) - data->error = MMC_ERR_BADCRC; + data->error = -EILSEQ; /* Clear the CRC bits */ au_writel(SD_STATUS_WC | SD_STATUS_RC, HOST_STATUS(host)); data->bytes_xfered = 0; - if (data->error == MMC_ERR_NONE) { + if (!data->error) { if (host->flags & HOST_F_DMA) { u32 chan = DMA_CHANNEL(host); @@ -475,7 +475,7 @@ static void au1xmmc_cmd_complete(struct au1xmmc_host *host, u32 status) return; cmd = mrq->cmd; - cmd->error = MMC_ERR_NONE; + cmd->error = 0; if (cmd->flags & MMC_RSP_PRESENT) { if (cmd->flags & MMC_RSP_136) { @@ -512,11 +512,11 @@ static void au1xmmc_cmd_complete(struct au1xmmc_host *host, u32 status) /* Figure out errors */ if (status & (SD_STATUS_SC | SD_STATUS_WC | SD_STATUS_RC)) - cmd->error = MMC_ERR_BADCRC; + cmd->error = -EILSEQ; trans = host->flags & (HOST_F_XMIT | HOST_F_RECV); - if (!trans || cmd->error != MMC_ERR_NONE) { + if (!trans || cmd->error) { IRQ_OFF(host, SD_CONFIG_TH | SD_CONFIG_RA|SD_CONFIG_RF); tasklet_schedule(&host->finish_task); @@ -589,7 +589,7 @@ au1xmmc_prepare_data(struct au1xmmc_host *host, struct mmc_data *data) data->sg_len, host->dma.dir); if (host->dma.len == 0) - return MMC_ERR_TIMEOUT; + return -ETIMEDOUT; au_writel(data->blksz - 1, HOST_BLKSIZE(host)); @@ -640,11 +640,11 @@ au1xmmc_prepare_data(struct au1xmmc_host *host, struct mmc_data *data) //IRQ_ON(host, SD_CONFIG_RA|SD_CONFIG_RF); } - return MMC_ERR_NONE; + return 0; dataerr: dma_unmap_sg(mmc_dev(host->mmc),data->sg,data->sg_len,host->dma.dir); - return MMC_ERR_TIMEOUT; + return -ETIMEDOUT; } /* static void au1xmmc_request @@ -656,7 +656,7 @@ static void au1xmmc_request(struct mmc_host* mmc, struct mmc_request* mrq) struct au1xmmc_host *host = mmc_priv(mmc); unsigned int flags = 0; - int ret = MMC_ERR_NONE; + int ret = 0; WARN_ON(irqs_disabled()); WARN_ON(host->status != HOST_S_IDLE); @@ -672,10 +672,10 @@ static void au1xmmc_request(struct mmc_host* mmc, struct mmc_request* mrq) ret = au1xmmc_prepare_data(host, mrq->data); } - if (ret == MMC_ERR_NONE) + if (!ret) ret = au1xmmc_send_command(host, 0, mrq->cmd, flags); - if (ret != MMC_ERR_NONE) { + if (ret) { mrq->cmd->error = ret; au1xmmc_finish_request(host); } @@ -764,10 +764,10 @@ static irqreturn_t au1xmmc_irq(int irq, void *dev_id) if (host->mrq && (status & STATUS_TIMEOUT)) { if (status & SD_STATUS_RAT) - host->mrq->cmd->error = MMC_ERR_TIMEOUT; + host->mrq->cmd->error = -ETIMEDOUT; else if (status & SD_STATUS_DT) - host->mrq->data->error = MMC_ERR_TIMEOUT; + host->mrq->data->error = -ETIMEDOUT; /* In PIO mode, interrupts might still be enabled */ IRQ_OFF(host, SD_CONFIG_NE | SD_CONFIG_TH); diff --git a/drivers/mmc/host/imxmmc.c b/drivers/mmc/host/imxmmc.c index 54bfc9f2559..04458c34281 100644 --- a/drivers/mmc/host/imxmmc.c +++ b/drivers/mmc/host/imxmmc.c @@ -428,11 +428,11 @@ static int imxmci_finish_data(struct imxmci_host *host, unsigned int stat) if ( stat & STATUS_ERR_MASK ) { dev_dbg(mmc_dev(host->mmc), "request failed. status: 0x%08x\n",stat); if(stat & (STATUS_CRC_READ_ERR | STATUS_CRC_WRITE_ERR)) - data->error = MMC_ERR_BADCRC; + data->error = -EILSEQ; else if(stat & STATUS_TIME_OUT_READ) - data->error = MMC_ERR_TIMEOUT; + data->error = -ETIMEDOUT; else - data->error = MMC_ERR_FAILED; + data->error = -EIO; } else { data->bytes_xfered = host->dma_size; } @@ -458,10 +458,10 @@ static int imxmci_cmd_done(struct imxmci_host *host, unsigned int stat) if (stat & STATUS_TIME_OUT_RESP) { dev_dbg(mmc_dev(host->mmc), "CMD TIMEOUT\n"); - cmd->error = MMC_ERR_TIMEOUT; + cmd->error = -ETIMEDOUT; } else if (stat & STATUS_RESP_CRC_ERR && cmd->flags & MMC_RSP_CRC) { dev_dbg(mmc_dev(host->mmc), "cmd crc error\n"); - cmd->error = MMC_ERR_BADCRC; + cmd->error = -EILSEQ; } if(cmd->flags & MMC_RSP_PRESENT) { @@ -482,7 +482,7 @@ static int imxmci_cmd_done(struct imxmci_host *host, unsigned int stat) dev_dbg(mmc_dev(host->mmc), "RESP 0x%08x, 0x%08x, 0x%08x, 0x%08x, error %d\n", cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3], cmd->error); - if (data && (cmd->error == MMC_ERR_NONE) && !(stat & STATUS_ERR_MASK)) { + if (data && !cmd->error && !(stat & STATUS_ERR_MASK)) { if (host->req->data->flags & MMC_DATA_WRITE) { /* Wait for FIFO to be empty before starting DMA write */ @@ -491,7 +491,7 @@ static int imxmci_cmd_done(struct imxmci_host *host, unsigned int stat) if(imxmci_busy_wait_for_status(host, &stat, STATUS_APPL_BUFF_FE, 40, "imxmci_cmd_done DMA WR") < 0) { - cmd->error = MMC_ERR_FIFO; + cmd->error = -EIO; imxmci_finish_data(host, stat); if(host->req) imxmci_finish_request(host, host->req); diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index be730c0a035..d53e9a8bdaa 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -154,11 +154,11 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data, } if (status & (MCI_DATACRCFAIL|MCI_DATATIMEOUT|MCI_TXUNDERRUN|MCI_RXOVERRUN)) { if (status & MCI_DATACRCFAIL) - data->error = MMC_ERR_BADCRC; + data->error = -EILSEQ; else if (status & MCI_DATATIMEOUT) - data->error = MMC_ERR_TIMEOUT; + data->error = -ETIMEDOUT; else if (status & (MCI_TXUNDERRUN|MCI_RXOVERRUN)) - data->error = MMC_ERR_FIFO; + data->error = -EIO; status |= MCI_DATAEND; /* @@ -193,12 +193,12 @@ mmci_cmd_irq(struct mmci_host *host, struct mmc_command *cmd, cmd->resp[3] = readl(base + MMCIRESPONSE3); if (status & MCI_CMDTIMEOUT) { - cmd->error = MMC_ERR_TIMEOUT; + cmd->error = -ETIMEDOUT; } else if (status & MCI_CMDCRCFAIL && cmd->flags & MMC_RSP_CRC) { - cmd->error = MMC_ERR_BADCRC; + cmd->error = -EILSEQ; } - if (!cmd->data || cmd->error != MMC_ERR_NONE) { + if (!cmd->data || cmd->error) { if (host->data) mmci_stop_data(host); mmci_request_end(host, cmd->mrq); diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 0cf97edc5f5..60a67dfcda6 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -263,7 +263,7 @@ mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data) enum dma_data_direction dma_data_dir; BUG_ON(host->dma_ch < 0); - if (data->error != MMC_ERR_NONE) + if (data->error) omap_stop_dma(host->dma_ch); /* Release DMA channel lazily */ mod_timer(&host->dma_timer, jiffies + HZ); @@ -368,7 +368,7 @@ mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd) } } - if (host->data == NULL || cmd->error != MMC_ERR_NONE) { + if (host->data == NULL || cmd->error) { host->mrq = NULL; clk_disable(host->fclk); mmc_request_done(host->mmc, cmd->mrq); @@ -475,14 +475,14 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) if (status & OMAP_MMC_STAT_DATA_TOUT) { dev_dbg(mmc_dev(host->mmc), "data timeout\n"); if (host->data) { - host->data->error |= MMC_ERR_TIMEOUT; + host->data->error = -ETIMEDOUT; transfer_error = 1; } } if (status & OMAP_MMC_STAT_DATA_CRC) { if (host->data) { - host->data->error |= MMC_ERR_BADCRC; + host->data->error = -EILSEQ; dev_dbg(mmc_dev(host->mmc), "data CRC error, bytes left %d\n", host->total_bytes_left); @@ -504,7 +504,7 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) dev_err(mmc_dev(host->mmc), "command timeout, CMD %d\n", host->cmd->opcode); - host->cmd->error = MMC_ERR_TIMEOUT; + host->cmd->error = -ETIMEDOUT; end_command = 1; } } @@ -514,7 +514,7 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) dev_err(mmc_dev(host->mmc), "command CRC error (CMD%d, arg 0x%08x)\n", host->cmd->opcode, host->cmd->arg); - host->cmd->error = MMC_ERR_BADCRC; + host->cmd->error = -EILSEQ; end_command = 1; } else dev_err(mmc_dev(host->mmc), diff --git a/drivers/mmc/host/pxamci.c b/drivers/mmc/host/pxamci.c index ff960334b33..b89e32d1e9b 100644 --- a/drivers/mmc/host/pxamci.c +++ b/drivers/mmc/host/pxamci.c @@ -226,7 +226,7 @@ static int pxamci_cmd_done(struct pxamci_host *host, unsigned int stat) } if (stat & STAT_TIME_OUT_RESPONSE) { - cmd->error = MMC_ERR_TIMEOUT; + cmd->error = -ETIMEDOUT; } else if (stat & STAT_RES_CRC_ERR && cmd->flags & MMC_RSP_CRC) { #ifdef CONFIG_PXA27x /* @@ -239,11 +239,11 @@ static int pxamci_cmd_done(struct pxamci_host *host, unsigned int stat) pr_debug("ignoring CRC from command %d - *risky*\n", cmd->opcode); } else #endif - cmd->error = MMC_ERR_BADCRC; + cmd->error = -EILSEQ; } pxamci_disable_irq(host, END_CMD_RES); - if (host->data && cmd->error == MMC_ERR_NONE) { + if (host->data && !cmd->error) { pxamci_enable_irq(host, DATA_TRAN_DONE); } else { pxamci_finish_request(host, host->mrq); @@ -264,9 +264,9 @@ static int pxamci_data_done(struct pxamci_host *host, unsigned int stat) host->dma_dir); if (stat & STAT_READ_TIME_OUT) - data->error = MMC_ERR_TIMEOUT; + data->error = -ETIMEDOUT; else if (stat & (STAT_CRC_READ_ERROR|STAT_CRC_WRITE_ERROR)) - data->error = MMC_ERR_BADCRC; + data->error = -EILSEQ; /* * There appears to be a hardware design bug here. There seems to @@ -274,7 +274,7 @@ static int pxamci_data_done(struct pxamci_host *host, unsigned int stat) * This means that if there was an error on any block, we mark all * data blocks as being in error. */ - if (data->error == MMC_ERR_NONE) + if (!data->error) data->bytes_xfered = data->blocks * data->blksz; else data->bytes_xfered = 0; diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 20a7d89e01b..479d6a265dd 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -481,16 +481,16 @@ static void sdhci_finish_data(struct sdhci_host *host) * Controller doesn't count down when in single block mode. */ if (data->blocks == 1) - blocks = (data->error == MMC_ERR_NONE) ? 0 : 1; + blocks = (data->error == 0) ? 0 : 1; else blocks = readw(host->ioaddr + SDHCI_BLOCK_COUNT); data->bytes_xfered = data->blksz * (data->blocks - blocks); - if ((data->error == MMC_ERR_NONE) && blocks) { + if (!data->error && blocks) { printk(KERN_ERR "%s: Controller signalled completion even " "though there were blocks left.\n", mmc_hostname(host->mmc)); - data->error = MMC_ERR_FAILED; + data->error = -EIO; } if (data->stop) { @@ -498,7 +498,7 @@ static void sdhci_finish_data(struct sdhci_host *host) * The controller needs a reset of internal state machines * upon error conditions. */ - if (data->error != MMC_ERR_NONE) { + if (data->error) { sdhci_reset(host, SDHCI_RESET_CMD); sdhci_reset(host, SDHCI_RESET_DATA); } @@ -533,7 +533,7 @@ static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) printk(KERN_ERR "%s: Controller never released " "inhibit bit(s).\n", mmc_hostname(host->mmc)); sdhci_dumpregs(host); - cmd->error = MMC_ERR_FAILED; + cmd->error = -EIO; tasklet_schedule(&host->finish_tasklet); return; } @@ -554,7 +554,7 @@ static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) { printk(KERN_ERR "%s: Unsupported response type!\n", mmc_hostname(host->mmc)); - cmd->error = MMC_ERR_INVALID; + cmd->error = -EINVAL; tasklet_schedule(&host->finish_tasklet); return; } @@ -601,7 +601,7 @@ static void sdhci_finish_command(struct sdhci_host *host) } } - host->cmd->error = MMC_ERR_NONE; + host->cmd->error = 0; if (host->data && host->data_early) sdhci_finish_data(host); @@ -722,7 +722,7 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) host->mrq = mrq; if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) { - host->mrq->cmd->error = MMC_ERR_TIMEOUT; + host->mrq->cmd->error = -ENOMEDIUM; tasklet_schedule(&host->finish_tasklet); } else sdhci_send_command(host, mrq->cmd); @@ -831,7 +831,7 @@ static void sdhci_tasklet_card(unsigned long param) sdhci_reset(host, SDHCI_RESET_CMD); sdhci_reset(host, SDHCI_RESET_DATA); - host->mrq->cmd->error = MMC_ERR_FAILED; + host->mrq->cmd->error = -ENOMEDIUM; tasklet_schedule(&host->finish_tasklet); } } @@ -859,9 +859,9 @@ static void sdhci_tasklet_finish(unsigned long param) * The controller needs a reset of internal state machines * upon error conditions. */ - if ((mrq->cmd->error != MMC_ERR_NONE) || - (mrq->data && ((mrq->data->error != MMC_ERR_NONE) || - (mrq->data->stop && (mrq->data->stop->error != MMC_ERR_NONE))))) { + if (mrq->cmd->error || + (mrq->data && (mrq->data->error || + (mrq->data->stop && mrq->data->stop->error)))) { /* Some controllers need this kick or reset won't work here */ if (host->chip->quirks & SDHCI_QUIRK_CLOCK_BEFORE_RESET) { @@ -906,13 +906,13 @@ static void sdhci_timeout_timer(unsigned long data) sdhci_dumpregs(host); if (host->data) { - host->data->error = MMC_ERR_TIMEOUT; + host->data->error = -ETIMEDOUT; sdhci_finish_data(host); } else { if (host->cmd) - host->cmd->error = MMC_ERR_TIMEOUT; + host->cmd->error = -ETIMEDOUT; else - host->mrq->cmd->error = MMC_ERR_TIMEOUT; + host->mrq->cmd->error = -ETIMEDOUT; tasklet_schedule(&host->finish_tasklet); } @@ -941,13 +941,12 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask) } if (intmask & SDHCI_INT_TIMEOUT) - host->cmd->error = MMC_ERR_TIMEOUT; - else if (intmask & SDHCI_INT_CRC) - host->cmd->error = MMC_ERR_BADCRC; - else if (intmask & (SDHCI_INT_END_BIT | SDHCI_INT_INDEX)) - host->cmd->error = MMC_ERR_FAILED; + host->cmd->error = -ETIMEDOUT; + else if (intmask & (SDHCI_INT_CRC | SDHCI_INT_END_BIT | + SDHCI_INT_INDEX)) + host->cmd->error = -EILSEQ; - if (host->cmd->error != MMC_ERR_NONE) + if (host->cmd->error) tasklet_schedule(&host->finish_tasklet); else if (intmask & SDHCI_INT_RESPONSE) sdhci_finish_command(host); @@ -974,13 +973,11 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) } if (intmask & SDHCI_INT_DATA_TIMEOUT) - host->data->error = MMC_ERR_TIMEOUT; - else if (intmask & SDHCI_INT_DATA_CRC) - host->data->error = MMC_ERR_BADCRC; - else if (intmask & SDHCI_INT_DATA_END_BIT) - host->data->error = MMC_ERR_FAILED; + host->data->error = -ETIMEDOUT; + else if (intmask & (SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_END_BIT)) + host->data->error = -EILSEQ; - if (host->data->error != MMC_ERR_NONE) + if (host->data->error) sdhci_finish_data(host); else { if (intmask & (SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL)) diff --git a/drivers/mmc/host/tifm_sd.c b/drivers/mmc/host/tifm_sd.c index 8b736e96844..b4a56e5e513 100644 --- a/drivers/mmc/host/tifm_sd.c +++ b/drivers/mmc/host/tifm_sd.c @@ -404,14 +404,14 @@ static void tifm_sd_check_status(struct tifm_sd *host) struct tifm_dev *sock = host->dev; struct mmc_command *cmd = host->req->cmd; - if (cmd->error != MMC_ERR_NONE) + if (cmd->error) goto finish_request; if (!(host->cmd_flags & CMD_READY)) return; if (cmd->data) { - if (cmd->data->error != MMC_ERR_NONE) { + if (cmd->data->error) { if ((host->cmd_flags & SCMD_ACTIVE) && !(host->cmd_flags & SCMD_READY)) return; @@ -504,7 +504,7 @@ static void tifm_sd_card_event(struct tifm_dev *sock) { struct tifm_sd *host; unsigned int host_status = 0; - int cmd_error = MMC_ERR_NONE; + int cmd_error = 0; struct mmc_command *cmd = NULL; unsigned long flags; @@ -521,15 +521,15 @@ static void tifm_sd_card_event(struct tifm_dev *sock) writel(host_status & TIFM_MMCSD_ERRMASK, sock->addr + SOCK_MMCSD_STATUS); if (host_status & TIFM_MMCSD_CTO) - cmd_error = MMC_ERR_TIMEOUT; + cmd_error = -ETIMEDOUT; else if (host_status & TIFM_MMCSD_CCRC) - cmd_error = MMC_ERR_BADCRC; + cmd_error = -EILSEQ; if (cmd->data) { if (host_status & TIFM_MMCSD_DTO) - cmd->data->error = MMC_ERR_TIMEOUT; + cmd->data->error = -ETIMEDOUT; else if (host_status & TIFM_MMCSD_DCRC) - cmd->data->error = MMC_ERR_BADCRC; + cmd->data->error = -EILSEQ; } writel(TIFM_FIFO_INT_SETALL, @@ -722,7 +722,7 @@ static void tifm_sd_request(struct mmc_host *mmc, struct mmc_request *mrq) return; err_out: - mrq->cmd->error = MMC_ERR_TIMEOUT; + mrq->cmd->error = -ETIMEDOUT; mmc_request_done(mmc, mrq); } @@ -1012,9 +1012,9 @@ static void tifm_sd_remove(struct tifm_dev *sock) writel(TIFM_FIFO_INT_SETALL, sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR); writel(0, sock->addr + SOCK_DMA_FIFO_INT_ENABLE_SET); - host->req->cmd->error = MMC_ERR_TIMEOUT; + host->req->cmd->error = -ENOMEDIUM; if (host->req->stop) - host->req->stop->error = MMC_ERR_TIMEOUT; + host->req->stop->error = -ENOMEDIUM; tasklet_schedule(&host->finish_tasklet); } spin_unlock_irqrestore(&sock->lock, flags); diff --git a/drivers/mmc/host/wbsd.c b/drivers/mmc/host/wbsd.c index 9bf2a877113..44968c2279e 100644 --- a/drivers/mmc/host/wbsd.c +++ b/drivers/mmc/host/wbsd.c @@ -317,7 +317,7 @@ static inline void wbsd_get_short_reply(struct wbsd_host *host, * Correct response type? */ if (wbsd_read_index(host, WBSD_IDX_RSPLEN) != WBSD_RSP_SHORT) { - cmd->error = MMC_ERR_INVALID; + cmd->error = -EILSEQ; return; } @@ -337,7 +337,7 @@ static inline void wbsd_get_long_reply(struct wbsd_host *host, * Correct response type? */ if (wbsd_read_index(host, WBSD_IDX_RSPLEN) != WBSD_RSP_LONG) { - cmd->error = MMC_ERR_INVALID; + cmd->error = -EILSEQ; return; } @@ -372,7 +372,7 @@ static void wbsd_send_command(struct wbsd_host *host, struct mmc_command *cmd) for (i = 3; i >= 0; i--) outb((cmd->arg >> (i * 8)) & 0xff, host->base + WBSD_CMDR); - cmd->error = MMC_ERR_NONE; + cmd->error = 0; /* * Wait for the request to complete. @@ -392,13 +392,13 @@ static void wbsd_send_command(struct wbsd_host *host, struct mmc_command *cmd) /* Card removed? */ if (isr & WBSD_INT_CARD) - cmd->error = MMC_ERR_TIMEOUT; + cmd->error = -ENOMEDIUM; /* Timeout? */ else if (isr & WBSD_INT_TIMEOUT) - cmd->error = MMC_ERR_TIMEOUT; + cmd->error = -ETIMEDOUT; /* CRC? */ else if ((cmd->flags & MMC_RSP_CRC) && (isr & WBSD_INT_CRC)) - cmd->error = MMC_ERR_BADCRC; + cmd->error = -EILSEQ; /* All ok */ else { if (cmd->flags & MMC_RSP_136) @@ -585,7 +585,7 @@ static void wbsd_prepare_data(struct wbsd_host *host, struct mmc_data *data) ((blksize >> 4) & 0xF0) | WBSD_DATA_WIDTH); wbsd_write_index(host, WBSD_IDX_PBSLSB, blksize & 0xFF); } else { - data->error = MMC_ERR_INVALID; + data->error = -EINVAL; return; } @@ -607,7 +607,7 @@ static void wbsd_prepare_data(struct wbsd_host *host, struct mmc_data *data) */ BUG_ON(size > 0x10000); if (size > 0x10000) { - data->error = MMC_ERR_INVALID; + data->error = -EINVAL; return; } @@ -669,7 +669,7 @@ static void wbsd_prepare_data(struct wbsd_host *host, struct mmc_data *data) } } - data->error = MMC_ERR_NONE; + data->error = 0; } static void wbsd_finish_data(struct wbsd_host *host, struct mmc_data *data) @@ -724,8 +724,8 @@ static void wbsd_finish_data(struct wbsd_host *host, struct mmc_data *data) "%d bytes left.\n", mmc_hostname(host->mmc), count); - if (data->error == MMC_ERR_NONE) - data->error = MMC_ERR_FAILED; + if (!data->error) + data->error = -EIO; } else { /* * Transfer data from DMA buffer to @@ -735,7 +735,7 @@ static void wbsd_finish_data(struct wbsd_host *host, struct mmc_data *data) wbsd_dma_to_sg(host, data); } - if (data->error != MMC_ERR_NONE) { + if (data->error) { if (data->bytes_xfered) data->bytes_xfered -= data->blksz; } @@ -767,11 +767,10 @@ static void wbsd_request(struct mmc_host *mmc, struct mmc_request *mrq) host->mrq = mrq; /* - * If there is no card in the slot then - * timeout immediatly. + * Check that there is actually a card in the slot. */ if (!(host->flags & WBSD_FCARD_PRESENT)) { - cmd->error = MMC_ERR_TIMEOUT; + cmd->error = -ENOMEDIUM; goto done; } @@ -807,7 +806,7 @@ static void wbsd_request(struct mmc_host *mmc, struct mmc_request *mrq) "supported by this controller.\n", mmc_hostname(host->mmc), cmd->opcode); #endif - cmd->error = MMC_ERR_INVALID; + cmd->error = -EINVAL; goto done; }; @@ -819,7 +818,7 @@ static void wbsd_request(struct mmc_host *mmc, struct mmc_request *mrq) if (cmd->data) { wbsd_prepare_data(host, cmd->data); - if (cmd->data->error != MMC_ERR_NONE) + if (cmd->data->error) goto done; } @@ -830,7 +829,7 @@ static void wbsd_request(struct mmc_host *mmc, struct mmc_request *mrq) * will be finished after the data has * transfered. */ - if (cmd->data && (cmd->error == MMC_ERR_NONE)) { + if (cmd->data && !cmd->error) { /* * Dirty fix for hardware bug. */ @@ -1033,7 +1032,7 @@ static void wbsd_tasklet_card(unsigned long param) mmc_hostname(host->mmc)); wbsd_reset(host); - host->mrq->cmd->error = MMC_ERR_FAILED; + host->mrq->cmd->error = -ENOMEDIUM; tasklet_schedule(&host->finish_tasklet); } @@ -1097,7 +1096,7 @@ static void wbsd_tasklet_crc(unsigned long param) DBGF("CRC error\n"); - data->error = MMC_ERR_BADCRC; + data->error = -EILSEQ; tasklet_schedule(&host->finish_tasklet); @@ -1121,7 +1120,7 @@ static void wbsd_tasklet_timeout(unsigned long param) DBGF("Timeout\n"); - data->error = MMC_ERR_TIMEOUT; + data->error = -ETIMEDOUT; tasklet_schedule(&host->finish_tasklet); -- cgit v1.2.3 From adf66a0dc5e8be8d4e64f3c2114f9b175558235b Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sun, 22 Jul 2007 23:08:30 +0200 Subject: mmc: improve error code feedback Now that we use "normal" error codes, improve the reporting and response to error codes in the core. Signed-off-by: Pierre Ossman --- drivers/mmc/core/mmc.c | 27 +++++++++++++++++++-------- drivers/mmc/core/sd.c | 22 +++++++++++++++++----- 2 files changed, 36 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index fe483d5af74..258fe73eeaa 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -176,13 +176,19 @@ static int mmc_read_ext_csd(struct mmc_card *card) ext_csd = kmalloc(512, GFP_KERNEL); if (!ext_csd) { printk(KERN_ERR "%s: could not allocate a buffer to " - "receive the ext_csd. mmc v4 cards will be " - "treated as v3.\n", mmc_hostname(card->host)); + "receive the ext_csd.\n", mmc_hostname(card->host)); return -ENOMEM; } err = mmc_send_ext_csd(card, ext_csd); if (err) { + /* + * We all hosts that cannot perform the command + * to fail more gracefully + */ + if (err != -EINVAL) + goto out; + /* * High capacity cards should have this "magic" size * stored in their CSD. @@ -199,6 +205,7 @@ static int mmc_read_ext_csd(struct mmc_card *card) mmc_hostname(card->host)); err = 0; } + goto out; } @@ -269,8 +276,10 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, goto err; if (oldcard) { - if (memcmp(cid, oldcard->raw_cid, sizeof(cid)) != 0) + if (memcmp(cid, oldcard->raw_cid, sizeof(cid)) != 0) { + err = -ENOENT; goto err; + } card = oldcard; } else { @@ -278,8 +287,10 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, * Allocate card structure. */ card = mmc_alloc_card(host); - if (IS_ERR(card)) + if (IS_ERR(card)) { + err = PTR_ERR(card); goto err; + } card->type = MMC_TYPE_MMC; card->rca = 1; @@ -304,10 +315,10 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, goto free_card; err = mmc_decode_csd(card); - if (err < 0) + if (err) goto free_card; err = mmc_decode_cid(card); - if (err < 0) + if (err) goto free_card; } @@ -379,7 +390,7 @@ free_card: mmc_remove_card(card); err: - return -EIO; + return err; } /* @@ -587,6 +598,6 @@ err: printk(KERN_ERR "%s: error %d whilst initialising MMC card\n", mmc_hostname(host), err); - return 0; + return err; } diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 00895c99d9b..0a04a6e86ca 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -213,10 +213,18 @@ static int mmc_read_switch(struct mmc_card *card) err = mmc_sd_switch(card, 0, 0, 1, status); if (err) { + /* + * We all hosts that cannot perform the command + * to fail more gracefully + */ + if (err != -EINVAL) + goto out; + printk(KERN_WARNING "%s: problem reading switch " "capabilities, performance might suffer.\n", mmc_hostname(card->host)); err = 0; + goto out; } @@ -324,8 +332,10 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, goto err; if (oldcard) { - if (memcmp(cid, oldcard->raw_cid, sizeof(cid)) != 0) + if (memcmp(cid, oldcard->raw_cid, sizeof(cid)) != 0) { + err = -ENOENT; goto err; + } card = oldcard; } else { @@ -333,8 +343,10 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, * Allocate card structure. */ card = mmc_alloc_card(host); - if (IS_ERR(card)) + if (IS_ERR(card)) { + err = PTR_ERR(card); goto err; + } card->type = MMC_TYPE_SD; memcpy(card->raw_cid, cid, sizeof(card->raw_cid)); @@ -358,7 +370,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, goto free_card; err = mmc_decode_csd(card); - if (err < 0) + if (err) goto free_card; mmc_decode_cid(card); @@ -449,7 +461,7 @@ free_card: mmc_remove_card(card); err: - return -EIO; + return err; } /* @@ -666,6 +678,6 @@ err: printk(KERN_ERR "%s: error %d whilst initialising SD card\n", mmc_hostname(host), err); - return 0; + return err; } -- cgit v1.2.3 From d7604d76351f7745d0e62d9f2bbcbb917c9013f3 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 23 Jul 2007 00:34:07 +0200 Subject: mmc: read ext_csd version number Make sure we do not try to parse a structure we do not understand. Signed-off-by: Pierre Ossman --- drivers/mmc/core/mmc.c | 24 +++++++++++++++++------- include/linux/mmc/mmc.h | 1 + 2 files changed, 18 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 258fe73eeaa..cdc38b43b79 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -161,6 +161,7 @@ static int mmc_read_ext_csd(struct mmc_card *card) { int err; u8 *ext_csd; + unsigned int ext_csd_struct; BUG_ON(!card); @@ -209,13 +210,22 @@ static int mmc_read_ext_csd(struct mmc_card *card) goto out; } - card->ext_csd.sectors = - ext_csd[EXT_CSD_SEC_CNT + 0] << 0 | - ext_csd[EXT_CSD_SEC_CNT + 1] << 8 | - ext_csd[EXT_CSD_SEC_CNT + 2] << 16 | - ext_csd[EXT_CSD_SEC_CNT + 3] << 24; - if (card->ext_csd.sectors) - mmc_card_set_blockaddr(card); + ext_csd_struct = ext_csd[EXT_CSD_REV]; + if (ext_csd_struct > 2) { + printk("%s: unrecognised EXT_CSD structure version %d\n", + mmc_hostname(card->host), ext_csd_struct); + return -EINVAL; + } + + if (ext_csd_struct >= 2) { + card->ext_csd.sectors = + ext_csd[EXT_CSD_SEC_CNT + 0] << 0 | + ext_csd[EXT_CSD_SEC_CNT + 1] << 8 | + ext_csd[EXT_CSD_SEC_CNT + 2] << 16 | + ext_csd[EXT_CSD_SEC_CNT + 3] << 24; + if (card->ext_csd.sectors) + mmc_card_set_blockaddr(card); + } switch (ext_csd[EXT_CSD_CARD_TYPE]) { case EXT_CSD_CARD_TYPE_52 | EXT_CSD_CARD_TYPE_26: diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index e3ed9b95040..d1d6cbcc151 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -227,6 +227,7 @@ struct _mmc_csd { #define EXT_CSD_BUS_WIDTH 183 /* R/W */ #define EXT_CSD_HS_TIMING 185 /* R/W */ #define EXT_CSD_CARD_TYPE 196 /* RO */ +#define EXT_CSD_REV 192 /* RO */ #define EXT_CSD_SEC_CNT 212 /* RO, 4 bytes */ /* -- cgit v1.2.3 From b146d26a61e0feab2f12a98ae83fd352830899c0 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 24 Jul 2007 19:16:54 +0200 Subject: mmc: mmc_set_data_timeout() parameter write is redundant The write parameter in mmc_set_data_timeout() is redundant as the data structure contains information about the direction of the transfer. Signed-off-by: Pierre Ossman --- drivers/mmc/card/block.c | 4 ++-- drivers/mmc/core/core.c | 8 +++----- drivers/mmc/core/mmc_ops.c | 2 +- drivers/mmc/core/sd_ops.c | 4 ++-- include/linux/mmc/core.h | 2 +- 5 files changed, 9 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 0da341acf32..9abf29f8435 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -229,8 +229,6 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) if (brq.data.blocks > card->host->max_blk_count) brq.data.blocks = card->host->max_blk_count; - mmc_set_data_timeout(&brq.data, card, rq_data_dir(req) != READ); - /* * If the host doesn't support multiple block writes, force * block writes to single block. SD cards are excepted from @@ -261,6 +259,8 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) brq.data.flags |= MMC_DATA_WRITE; } + mmc_set_data_timeout(&brq.data, card); + brq.data.sg = mq->sg; brq.data.sg_len = mmc_queue_map_sg(mq); diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 63b67296e92..51e611f2f33 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -220,13 +220,11 @@ EXPORT_SYMBOL(mmc_wait_for_cmd); * mmc_set_data_timeout - set the timeout for a data command * @data: data phase for command * @card: the MMC card associated with the data transfer - * @write: flag to differentiate reads from writes * * Computes the data timeout parameters according to the * correct algorithm given the card type. */ -void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card, - int write) +void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card) { unsigned int mult; @@ -239,7 +237,7 @@ void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card, * Scale up the multiplier (and therefore the timeout) by * the r2w factor for writes. */ - if (write) + if (data->flags & MMC_DATA_WRITE) mult <<= card->csd.r2w_factor; data->timeout_ns = card->csd.tacc_ns * mult; @@ -255,7 +253,7 @@ void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card, timeout_us += data->timeout_clks * 1000 / (card->host->ios.clock / 1000); - if (write) + if (data->flags & MMC_DATA_WRITE) limit_us = 250000; else limit_us = 100000; diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index 15cd575effa..39567f91a4b 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -214,7 +214,7 @@ int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd) sg_init_one(&sg, ext_csd, 512); - mmc_set_data_timeout(&data, card, 0); + mmc_set_data_timeout(&data, card); mmc_wait_for_req(card->host, &mrq); diff --git a/drivers/mmc/core/sd_ops.c b/drivers/mmc/core/sd_ops.c index b4d43bd0fed..491e0306b1b 100644 --- a/drivers/mmc/core/sd_ops.c +++ b/drivers/mmc/core/sd_ops.c @@ -252,7 +252,7 @@ int mmc_app_send_scr(struct mmc_card *card, u32 *scr) sg_init_one(&sg, scr, 8); - mmc_set_data_timeout(&data, card, 0); + mmc_set_data_timeout(&data, card); mmc_wait_for_req(card->host, &mrq); @@ -302,7 +302,7 @@ int mmc_sd_switch(struct mmc_card *card, int mode, int group, sg_init_one(&sg, resp, 64); - mmc_set_data_timeout(&data, card, 0); + mmc_set_data_timeout(&data, card); mmc_wait_for_req(card->host, &mrq); diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index a2b79518f05..2a03f8b9cdf 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -111,7 +111,7 @@ extern int mmc_wait_for_cmd(struct mmc_host *, struct mmc_command *, int); extern int mmc_wait_for_app_cmd(struct mmc_host *, struct mmc_card *, struct mmc_command *, int); -extern void mmc_set_data_timeout(struct mmc_data *, const struct mmc_card *, int); +extern void mmc_set_data_timeout(struct mmc_data *, const struct mmc_card *); extern void mmc_claim_host(struct mmc_host *host); extern void mmc_release_host(struct mmc_host *host); -- cgit v1.2.3 From 255d01af9a990fd5166f04ed0cc0b30b7b67e81e Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 24 Jul 2007 20:38:53 +0200 Subject: mmc: remove BYTEBLOCK capability Remove the BYTEBLOCK capability and let the broken hosts fail the requests with -EINVAL instead. Signed-off-by: Pierre Ossman --- drivers/mmc/host/at91_mci.c | 1 - drivers/mmc/host/imxmmc.c | 2 +- drivers/mmc/host/mmci.c | 8 ++++++++ drivers/mmc/host/sdhci.c | 2 +- drivers/mmc/host/tifm_sd.c | 13 ++++++++++--- drivers/mmc/host/wbsd.c | 2 +- include/linux/mmc/host.h | 5 ++--- 7 files changed, 23 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/host/at91_mci.c b/drivers/mmc/host/at91_mci.c index 810a433ce53..576d7cb0b3e 100644 --- a/drivers/mmc/host/at91_mci.c +++ b/drivers/mmc/host/at91_mci.c @@ -834,7 +834,6 @@ static int __init at91_mci_probe(struct platform_device *pdev) mmc->f_min = 375000; mmc->f_max = 25000000; mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; - mmc->caps = MMC_CAP_BYTEBLOCK; mmc->max_blk_size = 4095; mmc->max_blk_count = mmc->max_req_size; diff --git a/drivers/mmc/host/imxmmc.c b/drivers/mmc/host/imxmmc.c index 04458c34281..e33c123c702 100644 --- a/drivers/mmc/host/imxmmc.c +++ b/drivers/mmc/host/imxmmc.c @@ -963,7 +963,7 @@ static int imxmci_probe(struct platform_device *pdev) mmc->f_min = 150000; mmc->f_max = CLK_RATE/2; mmc->ocr_avail = MMC_VDD_32_33; - mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_BYTEBLOCK; + mmc->caps = MMC_CAP_4_BIT_DATA; /* MMC core transfer sizes tunable parameters */ mmc->max_hw_segs = 64; diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index d53e9a8bdaa..4a72772f4fe 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -391,6 +391,14 @@ static void mmci_request(struct mmc_host *mmc, struct mmc_request *mrq) WARN_ON(host->mrq != NULL); + if (mrq->data && (hweight32(mrq->data->blksz) > 1)) { + printk(KERN_ERR "%s: Unsupported block size (%d bytes)\n", + mmc_hostname(mmc), mrq->data->blksz); + mrq->cmd->error = -EINVAL; + mmc_request_done(mmc, mrq); + return; + } + spin_lock_irq(&host->lock); host->mrq = mrq; diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 479d6a265dd..c63edc5c17e 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1309,7 +1309,7 @@ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot) mmc->ops = &sdhci_ops; mmc->f_min = host->max_clk / 256; mmc->f_max = host->max_clk; - mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_MULTIWRITE | MMC_CAP_BYTEBLOCK; + mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_MULTIWRITE; if (caps & SDHCI_CAN_DO_HISPD) mmc->caps |= MMC_CAP_SD_HIGHSPEED; diff --git a/drivers/mmc/host/tifm_sd.c b/drivers/mmc/host/tifm_sd.c index b4a56e5e513..951392d2ce3 100644 --- a/drivers/mmc/host/tifm_sd.c +++ b/drivers/mmc/host/tifm_sd.c @@ -626,14 +626,21 @@ static void tifm_sd_request(struct mmc_host *mmc, struct mmc_request *mrq) spin_lock_irqsave(&sock->lock, flags); if (host->eject) { - spin_unlock_irqrestore(&sock->lock, flags); + mrq->cmd->error = -ENOMEDIUM; goto err_out; } if (host->req) { printk(KERN_ERR "%s : unfinished request detected\n", sock->dev.bus_id); - spin_unlock_irqrestore(&sock->lock, flags); + mrq->cmd->error = -ETIMEDOUT; + goto err_out; + } + + if (mrq->data && (hweight32(mrq->data->blksz) > 1)) { + printk(KERN_ERR "%s: Unsupported block size (%d bytes)\n", + sock->dev.bus_id, mrq->data->blksz); + mrq->cmd->error = -EINVAL; goto err_out; } @@ -722,7 +729,7 @@ static void tifm_sd_request(struct mmc_host *mmc, struct mmc_request *mrq) return; err_out: - mrq->cmd->error = -ETIMEDOUT; + spin_unlock_irqrestore(&sock->lock, flags); mmc_request_done(mmc, mrq); } diff --git a/drivers/mmc/host/wbsd.c b/drivers/mmc/host/wbsd.c index 44968c2279e..80db11c05f2 100644 --- a/drivers/mmc/host/wbsd.c +++ b/drivers/mmc/host/wbsd.c @@ -1219,7 +1219,7 @@ static int __devinit wbsd_alloc_mmc(struct device *dev) mmc->f_min = 375000; mmc->f_max = 24000000; mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; - mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_MULTIWRITE | MMC_CAP_BYTEBLOCK; + mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_MULTIWRITE; spin_lock_init(&host->lock); diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index b1350dfd3e9..8e2642ebf02 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -87,9 +87,8 @@ struct mmc_host { #define MMC_CAP_4_BIT_DATA (1 << 0) /* Can the host do 4 bit transfers */ #define MMC_CAP_MULTIWRITE (1 << 1) /* Can accurately report bytes sent to card on error */ -#define MMC_CAP_BYTEBLOCK (1 << 2) /* Can do non-log2 block sizes */ -#define MMC_CAP_MMC_HIGHSPEED (1 << 3) /* Can do MMC high-speed timing */ -#define MMC_CAP_SD_HIGHSPEED (1 << 4) /* Can do SD high-speed timing */ +#define MMC_CAP_MMC_HIGHSPEED (1 << 2) /* Can do MMC high-speed timing */ +#define MMC_CAP_SD_HIGHSPEED (1 << 3) /* Can do SD high-speed timing */ /* host specific block data */ unsigned int max_seg_size; /* see blk_queue_max_segment_size */ -- cgit v1.2.3 From be0192aae1aed3fbf172e3f9a22ec75392c1b175 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 24 Jul 2007 21:11:47 +0200 Subject: mmc: remove confusing flag The MMC_DATA_MULTI flag never had a proper definition of what it means, so remove it and let the drivers check the block count in the request. Signed-off-by: Pierre Ossman --- drivers/mmc/card/block.c | 1 - drivers/mmc/host/at91_mci.c | 4 ++-- drivers/mmc/host/au1xmmc.c | 26 ++++++++++++++------------ include/linux/mmc/core.h | 1 - 4 files changed, 16 insertions(+), 16 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 9abf29f8435..ab510689ecd 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -241,7 +241,6 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) brq.data.blocks = 1; if (brq.data.blocks > 1) { - brq.data.flags |= MMC_DATA_MULTI; brq.mrq.stop = &brq.stop; readcmd = MMC_READ_MULTIPLE_BLOCK; writecmd = MMC_WRITE_MULTIPLE_BLOCK; diff --git a/drivers/mmc/host/at91_mci.c b/drivers/mmc/host/at91_mci.c index 576d7cb0b3e..8ec31780234 100644 --- a/drivers/mmc/host/at91_mci.c +++ b/drivers/mmc/host/at91_mci.c @@ -328,7 +328,7 @@ static void at91_mci_handle_transmitted(struct at91mci_host *host) data = cmd->data; if (!data) return; - if (cmd->data->flags & MMC_DATA_MULTI) { + if (cmd->data->blocks > 1) { pr_debug("multiple write : wait for BLKE...\n"); at91_mci_write(host, AT91_MCI_IER, AT91_MCI_BLKE); } else @@ -439,7 +439,7 @@ static void at91_mci_send_command(struct at91mci_host *host, struct mmc_command if (data->flags & MMC_DATA_STREAM) cmdr |= AT91_MCI_TRTYP_STREAM; - if (data->flags & MMC_DATA_MULTI) + if (data->blocks > 1) cmdr |= AT91_MCI_TRTYP_MULTIPLE; } else { diff --git a/drivers/mmc/host/au1xmmc.c b/drivers/mmc/host/au1xmmc.c index 49b0367e57c..92c4d0dfee4 100644 --- a/drivers/mmc/host/au1xmmc.c +++ b/drivers/mmc/host/au1xmmc.c @@ -186,7 +186,7 @@ static void au1xmmc_tasklet_finish(unsigned long param) } static int au1xmmc_send_command(struct au1xmmc_host *host, int wait, - struct mmc_command *cmd, unsigned int flags) + struct mmc_command *cmd, struct mmc_data *data) { u32 mmccmd = (cmd->opcode << SD_CMD_CI_SHIFT); @@ -211,16 +211,18 @@ static int au1xmmc_send_command(struct au1xmmc_host *host, int wait, return -EINVAL; } - if (flags & MMC_DATA_READ) { - if (flags & MMC_DATA_MULTI) - mmccmd |= SD_CMD_CT_4; - else - mmccmd |= SD_CMD_CT_2; - } else if (flags & MMC_DATA_WRITE) { - if (flags & MMC_DATA_MULTI) - mmccmd |= SD_CMD_CT_3; - else - mmccmd |= SD_CMD_CT_1; + if (data) { + if (flags & MMC_DATA_READ) { + if (data->blocks > 1) + mmccmd |= SD_CMD_CT_4; + else + mmccmd |= SD_CMD_CT_2; + } else if (flags & MMC_DATA_WRITE) { + if (data->blocks > 1) + mmccmd |= SD_CMD_CT_3; + else + mmccmd |= SD_CMD_CT_1; + } } au_writel(cmd->arg, HOST_CMDARG(host)); @@ -673,7 +675,7 @@ static void au1xmmc_request(struct mmc_host* mmc, struct mmc_request* mrq) } if (!ret) - ret = au1xmmc_send_command(host, 0, mrq->cmd, flags); + ret = au1xmmc_send_command(host, 0, mrq->cmd, mrq->data); if (ret) { mrq->cmd->error = ret; diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 2a03f8b9cdf..29c98ae10af 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -83,7 +83,6 @@ struct mmc_data { #define MMC_DATA_WRITE (1 << 8) #define MMC_DATA_READ (1 << 9) #define MMC_DATA_STREAM (1 << 10) -#define MMC_DATA_MULTI (1 << 11) unsigned int bytes_xfered; -- cgit v1.2.3 From 8fdd8521dcc9d89072f3ca5dca444560bcea39c6 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 24 Jul 2007 21:53:43 +0200 Subject: mmc: add missing printk levels Some printk:s were missing an explicit level. Signed-off-by: Pierre Ossman --- drivers/mmc/core/mmc.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index cdc38b43b79..ba7a847cfd0 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -212,8 +212,9 @@ static int mmc_read_ext_csd(struct mmc_card *card) ext_csd_struct = ext_csd[EXT_CSD_REV]; if (ext_csd_struct > 2) { - printk("%s: unrecognised EXT_CSD structure version %d\n", - mmc_hostname(card->host), ext_csd_struct); + printk(KERN_ERR "%s: unrecognised EXT_CSD structure " + "version %d\n", mmc_hostname(card->host), + ext_csd_struct); return -EINVAL; } -- cgit v1.2.3 From 1d4de9edd6c9ad676b20729ab15c04b78e9a50c5 Mon Sep 17 00:00:00 2001 From: Marc Pignat Date: Thu, 9 Aug 2007 13:56:29 +0200 Subject: mmc: at91_mci: disable handling of blocks with size not multiple of 4 bytes This kind of transfer is not supported, so don't advertise it and make it fail early. Signed-off-by: Marc Pignat Signed-off-by: Pierre Ossman --- drivers/mmc/host/at91_mci.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/host/at91_mci.c b/drivers/mmc/host/at91_mci.c index 8ec31780234..6ba98a49612 100644 --- a/drivers/mmc/host/at91_mci.c +++ b/drivers/mmc/host/at91_mci.c @@ -428,6 +428,14 @@ static void at91_mci_send_command(struct at91mci_host *host, struct mmc_command } if (data) { + + if ( data->blksz & 0x3 ) { + pr_debug("Unsupported block size\n"); + cmd->error = -EINVAL; + mmc_request_done(host->mmc, host->request); + return; + } + block_length = data->blksz; blocks = data->blocks; -- cgit v1.2.3 From 5c4e6f1301649d5b29dd0f70e6da83e728ab5ca5 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 21 May 2007 20:23:20 +0200 Subject: mmc: detect SDIO cards Really basic init sequence for SDIO cards. Signed-off-by: Pierre Ossman --- MAINTAINERS | 2 +- drivers/mmc/core/Makefile | 3 +- drivers/mmc/core/bus.c | 8 ++ drivers/mmc/core/core.c | 40 +++++++--- drivers/mmc/core/sdio.c | 176 ++++++++++++++++++++++++++++++++++++++++++++ drivers/mmc/core/sdio_ops.c | 49 ++++++++++++ drivers/mmc/core/sdio_ops.h | 18 +++++ include/linux/mmc/card.h | 2 + include/linux/mmc/core.h | 1 + include/linux/mmc/sdio.h | 19 +++++ 10 files changed, 304 insertions(+), 14 deletions(-) create mode 100644 drivers/mmc/core/sdio.c create mode 100644 drivers/mmc/core/sdio_ops.c create mode 100644 drivers/mmc/core/sdio_ops.h create mode 100644 include/linux/mmc/sdio.h (limited to 'drivers') diff --git a/MAINTAINERS b/MAINTAINERS index 9a91d9e3f1f..c22d34b11f6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2561,7 +2561,7 @@ L: linux-kernel@vger.kernel.org W: http://www.atnf.csiro.au/~rgooch/linux/kernel-patches.html S: Maintained -MULTIMEDIA CARD (MMC) AND SECURE DIGITAL (SD) SUBSYSTEM +MULTIMEDIA CARD (MMC), SECURE DIGITAL (SD) AND SDIO SUBSYSTEM P: Pierre Ossman M: drzeus-mmc@drzeus.cx L: linux-kernel@vger.kernel.org diff --git a/drivers/mmc/core/Makefile b/drivers/mmc/core/Makefile index 3fdd08c7f14..2fa5ebbc170 100644 --- a/drivers/mmc/core/Makefile +++ b/drivers/mmc/core/Makefile @@ -8,5 +8,6 @@ endif obj-$(CONFIG_MMC) += mmc_core.o mmc_core-y := core.o sysfs.o bus.o host.o \ - mmc.o mmc_ops.o sd.o sd_ops.o + mmc.o mmc_ops.o sd.o sd_ops.o \ + sdio.o sdio_ops.o diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index 817a79462b3..87a6070522f 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -34,6 +34,8 @@ static ssize_t mmc_type_show(struct device *dev, return sprintf(buf, "MMC\n"); case MMC_TYPE_SD: return sprintf(buf, "SD\n"); + case MMC_TYPE_SDIO: + return sprintf(buf, "SDIO\n"); default: return -EFAULT; } @@ -76,6 +78,9 @@ mmc_bus_uevent(struct device *dev, char **envp, int num_envp, char *buf, case MMC_TYPE_SD: add_env("MMC_TYPE=%s", "SD"); break; + case MMC_TYPE_SDIO: + add_env("MMC_TYPE=%s", "SDIO"); + break; } add_env("MMC_NAME=%s", mmc_card_name(card)); @@ -221,6 +226,9 @@ int mmc_add_card(struct mmc_card *card) if (mmc_card_blockaddr(card)) type = "SDHC"; break; + case MMC_TYPE_SDIO: + type = "SDIO"; + break; default: type = "?"; break; diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 51e611f2f33..092fa906ab8 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -32,9 +32,11 @@ #include "mmc_ops.h" #include "sd_ops.h" +#include "sdio_ops.h" extern int mmc_attach_mmc(struct mmc_host *host, u32 ocr); extern int mmc_attach_sd(struct mmc_host *host, u32 ocr); +extern int mmc_attach_sdio(struct mmc_host *host, u32 ocr); static struct workqueue_struct *workqueue; @@ -595,24 +597,38 @@ void mmc_rescan(struct work_struct *work) mmc_send_if_cond(host, host->ocr_avail); + /* + * First we search for SDIO... + */ + err = mmc_send_io_op_cond(host, 0, &ocr); + if (!err) { + if (mmc_attach_sdio(host, ocr)) + mmc_power_off(host); + return; + } + + /* + * ...then normal SD... + */ err = mmc_send_app_op_cond(host, 0, &ocr); if (!err) { if (mmc_attach_sd(host, ocr)) mmc_power_off(host); - } else { - /* - * If we fail to detect any SD cards then try - * searching for MMC cards. - */ - err = mmc_send_op_cond(host, 0, &ocr); - if (!err) { - if (mmc_attach_mmc(host, ocr)) - mmc_power_off(host); - } else { + return; + } + + /* + * ...and finally MMC. + */ + err = mmc_send_op_cond(host, 0, &ocr); + if (!err) { + if (mmc_attach_mmc(host, ocr)) mmc_power_off(host); - mmc_release_host(host); - } + return; } + + mmc_release_host(host); + mmc_power_off(host); } else { if (host->bus_ops->detect && !host->bus_dead) host->bus_ops->detect(host); diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c new file mode 100644 index 00000000000..ac0dd68df8e --- /dev/null +++ b/drivers/mmc/core/sdio.c @@ -0,0 +1,176 @@ +/* + * linux/drivers/mmc/sdio.c + * + * Copyright 2006-2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#include + +#include +#include + +#include "core.h" +#include "bus.h" +#include "mmc_ops.h" +#include "sd_ops.h" +#include "sdio_ops.h" + +/* + * Host is being removed. Free up the current card. + */ +static void mmc_sdio_remove(struct mmc_host *host) +{ + BUG_ON(!host); + BUG_ON(!host->card); + + mmc_remove_card(host->card); + host->card = NULL; +} + +/* + * Card detection callback from host. + */ +static void mmc_sdio_detect(struct mmc_host *host) +{ + int err; + + BUG_ON(!host); + BUG_ON(!host->card); + + mmc_claim_host(host); + + /* + * Just check if our card has been removed. + */ + err = mmc_select_card(host->card); + + mmc_release_host(host); + + if (err) { + mmc_sdio_remove(host); + + mmc_claim_host(host); + mmc_detach_bus(host); + mmc_release_host(host); + } +} + + +static const struct mmc_bus_ops mmc_sdio_ops = { + .remove = mmc_sdio_remove, + .detect = mmc_sdio_detect, +}; + + +/* + * Starting point for SDIO card init. + */ +int mmc_attach_sdio(struct mmc_host *host, u32 ocr) +{ + int err; + int funcs; + struct mmc_card *card; + + BUG_ON(!host); + BUG_ON(!host->claimed); + + mmc_attach_bus(host, &mmc_sdio_ops); + + /* + * Sanity check the voltages that the card claims to + * support. + */ + if (ocr & 0x7F) { + printk(KERN_WARNING "%s: card claims to support voltages " + "below the defined range. These will be ignored.\n", + mmc_hostname(host)); + ocr &= ~0x7F; + } + + if (ocr & MMC_VDD_165_195) { + printk(KERN_WARNING "%s: SDIO card claims to support the " + "incompletely defined 'low voltage range'. This " + "will be ignored.\n", mmc_hostname(host)); + ocr &= ~MMC_VDD_165_195; + } + + host->ocr = mmc_select_voltage(host, ocr); + + /* + * Can we support the voltage(s) of the card(s)? + */ + if (!host->ocr) { + err = -EINVAL; + goto err; + } + + /* + * Inform the card of the voltage + */ + err = mmc_send_io_op_cond(host, host->ocr, &ocr); + if (err) + goto err; + + /* + * The number of functions on the card is encoded inside + * the ocr. + */ + funcs = (ocr & 0x70000000) >> 28; + + /* + * Allocate card structure. + */ + card = mmc_alloc_card(host); + if (IS_ERR(card)) { + err = PTR_ERR(card); + goto err; + } + + card->type = MMC_TYPE_SDIO; + + /* + * Set card RCA. + */ + err = mmc_send_relative_addr(host, &card->rca); + if (err) + goto free_card; + + mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); + + /* + * Select card, as all following commands rely on that. + */ + err = mmc_select_card(card); + if (err) + goto free_card; + + host->card = card; + + mmc_release_host(host); + + err = mmc_add_card(host->card); + if (err) + goto reclaim_host; + + return 0; + +reclaim_host: + mmc_claim_host(host); +free_card: + mmc_remove_card(card); + host->card = NULL; +err: + mmc_detach_bus(host); + mmc_release_host(host); + + printk(KERN_ERR "%s: error %d whilst initialising SDIO card\n", + mmc_hostname(host), err); + + return err; +} + diff --git a/drivers/mmc/core/sdio_ops.c b/drivers/mmc/core/sdio_ops.c new file mode 100644 index 00000000000..d6f9f9d8517 --- /dev/null +++ b/drivers/mmc/core/sdio_ops.c @@ -0,0 +1,49 @@ +/* + * linux/drivers/mmc/sdio_ops.c + * + * Copyright 2006-2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#include +#include +#include + +#include "core.h" + +int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr) +{ + struct mmc_command cmd; + int i, err = 0; + + BUG_ON(!host); + + memset(&cmd, 0, sizeof(struct mmc_command)); + + cmd.opcode = SD_IO_SEND_OP_COND; + cmd.arg = ocr; + cmd.flags = MMC_RSP_R4 | MMC_CMD_BCR; + + for (i = 100; i; i--) { + err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES); + if (err) + break; + + if (cmd.resp[0] & MMC_CARD_BUSY || ocr == 0) + break; + + err = -ETIMEDOUT; + + mmc_delay(10); + } + + if (rocr) + *rocr = cmd.resp[0]; + + return err; +} + diff --git a/drivers/mmc/core/sdio_ops.h b/drivers/mmc/core/sdio_ops.h new file mode 100644 index 00000000000..d8c982976f1 --- /dev/null +++ b/drivers/mmc/core/sdio_ops.h @@ -0,0 +1,18 @@ +/* + * linux/drivers/mmc/sdio_ops.c + * + * Copyright 2006-2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#ifndef _MMC_SDIO_OPS_H +#define _MMC_SDIO_OPS_H + +int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr); + +#endif + diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index badf702fcff..43480ebebf9 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -67,6 +67,7 @@ struct mmc_card { unsigned int type; /* card type */ #define MMC_TYPE_MMC 0 /* MMC card */ #define MMC_TYPE_SD 1 /* SD card */ +#define MMC_TYPE_SDIO 2 /* SDIO card */ unsigned int state; /* (our) card state */ #define MMC_STATE_PRESENT (1<<0) /* present in sysfs */ #define MMC_STATE_READONLY (1<<1) /* card is read-only */ @@ -84,6 +85,7 @@ struct mmc_card { #define mmc_card_mmc(c) ((c)->type == MMC_TYPE_MMC) #define mmc_card_sd(c) ((c)->type == MMC_TYPE_SD) +#define mmc_card_sdio(c) ((c)->type == MMC_TYPE_SDIO) #define mmc_card_present(c) ((c)->state & MMC_STATE_PRESENT) #define mmc_card_readonly(c) ((c)->state & MMC_STATE_READONLY) diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 29c98ae10af..8faa436c557 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -41,6 +41,7 @@ struct mmc_command { #define MMC_RSP_R1B (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE|MMC_RSP_BUSY) #define MMC_RSP_R2 (MMC_RSP_PRESENT|MMC_RSP_136|MMC_RSP_CRC) #define MMC_RSP_R3 (MMC_RSP_PRESENT) +#define MMC_RSP_R4 (MMC_RSP_PRESENT) #define MMC_RSP_R6 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE) #define MMC_RSP_R7 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE) diff --git a/include/linux/mmc/sdio.h b/include/linux/mmc/sdio.h new file mode 100644 index 00000000000..d1a0b15cdfd --- /dev/null +++ b/include/linux/mmc/sdio.h @@ -0,0 +1,19 @@ +/* + * include/linux/mmc/sdio.h + * + * Copyright 2006-2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#ifndef MMC_SDIO_H +#define MMC_SDIO_H + +/* SDIO commands type argument response */ +#define SD_IO_SEND_OP_COND 5 /* bcr [23:0] OCR R4 */ + +#endif + -- cgit v1.2.3 From b2bcc798bbb482b2909801280f3c4aff8cbbf5be Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 22 May 2007 20:25:21 +0200 Subject: mmc: implement SDIO IO_RW_DIRECT operation Signed-off-by: Pierre Ossman --- drivers/mmc/core/sdio_ops.c | 37 +++++++++++++++++++++++++++++++++++++ drivers/mmc/core/sdio_ops.h | 2 ++ include/linux/mmc/core.h | 1 + include/linux/mmc/sdio.h | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/core/sdio_ops.c b/drivers/mmc/core/sdio_ops.c index d6f9f9d8517..31233f7b55c 100644 --- a/drivers/mmc/core/sdio_ops.c +++ b/drivers/mmc/core/sdio_ops.c @@ -10,6 +10,7 @@ */ #include +#include #include #include @@ -47,3 +48,39 @@ int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr) return err; } +int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn, + unsigned addr, u8 in, u8* out) +{ + struct mmc_command cmd; + int err; + + BUG_ON(!card); + BUG_ON(fn > 7); + + memset(&cmd, 0, sizeof(struct mmc_command)); + + cmd.opcode = SD_IO_RW_DIRECT; + cmd.arg = write ? 0x80000000 : 0x00000000; + cmd.arg |= fn << 28; + cmd.arg |= (write && out) ? 0x08000000 : 0x00000000; + cmd.arg |= addr << 9; + cmd.arg |= in; + cmd.flags = MMC_RSP_R5 | MMC_CMD_AC; + + err = mmc_wait_for_cmd(card->host, &cmd, 0); + if (err) + return err; + + if (cmd.resp[0] & R5_ERROR) + return -EIO; + if (cmd.resp[0] & R5_FUNCTION_NUMBER) + return -EINVAL; + if (cmd.resp[0] & R5_OUT_OF_RANGE) + return -ERANGE; + + if (out) + *out = cmd.resp[0] & 0xFF; + + return 0; +} + diff --git a/drivers/mmc/core/sdio_ops.h b/drivers/mmc/core/sdio_ops.h index d8c982976f1..f0e9d69e5ce 100644 --- a/drivers/mmc/core/sdio_ops.h +++ b/drivers/mmc/core/sdio_ops.h @@ -13,6 +13,8 @@ #define _MMC_SDIO_OPS_H int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr); +int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn, + unsigned addr, u8 in, u8* out); #endif diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 8faa436c557..43a92736be6 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -42,6 +42,7 @@ struct mmc_command { #define MMC_RSP_R2 (MMC_RSP_PRESENT|MMC_RSP_136|MMC_RSP_CRC) #define MMC_RSP_R3 (MMC_RSP_PRESENT) #define MMC_RSP_R4 (MMC_RSP_PRESENT) +#define MMC_RSP_R5 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE) #define MMC_RSP_R6 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE) #define MMC_RSP_R7 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE) diff --git a/include/linux/mmc/sdio.h b/include/linux/mmc/sdio.h index d1a0b15cdfd..e5f06de7d52 100644 --- a/include/linux/mmc/sdio.h +++ b/include/linux/mmc/sdio.h @@ -14,6 +14,40 @@ /* SDIO commands type argument response */ #define SD_IO_SEND_OP_COND 5 /* bcr [23:0] OCR R4 */ +#define SD_IO_RW_DIRECT 52 /* ac [31:0] See below R5 */ + +/* + * SD_IO_RW_DIRECT argument format: + * + * [31] R/W flag + * [30:28] Function number + * [27] RAW flag + * [25:9] Register address + * [7:0] Data + */ + +/* + SDIO status in R5 + Type + e : error bit + s : status bit + r : detected and set for the actual command response + x : detected and set during command execution. the host must poll + the card by sending status command in order to read these bits. + Clear condition + a : according to the card state + b : always related to the previous command. Reception of + a valid command will clear it (with a delay of one command) + c : clear by read + */ + +#define R5_COM_CRC_ERROR (1 << 15) /* er, b */ +#define R5_ILLEGAL_COMMAND (1 << 14) /* er, b */ +#define R5_ERROR (1 << 11) /* erx, c */ +#define R5_FUNCTION_NUMBER (1 << 9) /* er, c */ +#define R5_OUT_OF_RANGE (1 << 8) /* er, c */ +#define R5_STATUS(x) (x & 0xCB00) +#define R5_IO_CURRENT_STATE(x) ((x & 0x3000) >> 12) /* s, b */ #endif -- cgit v1.2.3 From e29a7d73f4277eb92aa64e17017dea33460828ef Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sat, 26 May 2007 13:48:18 +0200 Subject: mmc: basic SDIO device model Add the sdio bus type and basic device handling. Signed-off-by: Pierre Ossman --- drivers/mmc/core/Makefile | 2 +- drivers/mmc/core/core.c | 27 +++++++-- drivers/mmc/core/sdio.c | 72 ++++++++++++++++++++--- drivers/mmc/core/sdio_bus.c | 129 ++++++++++++++++++++++++++++++++++++++++++ drivers/mmc/core/sdio_bus.h | 22 +++++++ include/linux/mmc/card.h | 7 +++ include/linux/mmc/sdio_func.h | 35 ++++++++++++ 7 files changed, 279 insertions(+), 15 deletions(-) create mode 100644 drivers/mmc/core/sdio_bus.c create mode 100644 drivers/mmc/core/sdio_bus.h create mode 100644 include/linux/mmc/sdio_func.h (limited to 'drivers') diff --git a/drivers/mmc/core/Makefile b/drivers/mmc/core/Makefile index 2fa5ebbc170..71ab3d1e1eb 100644 --- a/drivers/mmc/core/Makefile +++ b/drivers/mmc/core/Makefile @@ -9,5 +9,5 @@ endif obj-$(CONFIG_MMC) += mmc_core.o mmc_core-y := core.o sysfs.o bus.o host.o \ mmc.o mmc_ops.o sd.o sd_ops.o \ - sdio.o sdio_ops.o + sdio.o sdio_ops.o sdio_bus.o diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 092fa906ab8..9747455928d 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -29,6 +29,7 @@ #include "core.h" #include "bus.h" #include "host.h" +#include "sdio_bus.h" #include "mmc_ops.h" #include "sd_ops.h" @@ -739,16 +740,32 @@ static int __init mmc_init(void) return -ENOMEM; ret = mmc_register_bus(); - if (ret == 0) { - ret = mmc_register_host_class(); - if (ret) - mmc_unregister_bus(); - } + if (ret) + goto destroy_workqueue; + + ret = mmc_register_host_class(); + if (ret) + goto unregister_bus; + + ret = sdio_register_bus(); + if (ret) + goto unregister_host_class; + + return 0; + +unregister_host_class: + mmc_unregister_host_class(); +unregister_bus: + mmc_unregister_bus(); +destroy_workqueue: + destroy_workqueue(workqueue); + return ret; } static void __exit mmc_exit(void) { + sdio_unregister_bus(); mmc_unregister_host_class(); mmc_unregister_bus(); destroy_workqueue(workqueue); diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index ac0dd68df8e..444328581ce 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -13,21 +13,49 @@ #include #include +#include #include "core.h" #include "bus.h" +#include "sdio_bus.h" #include "mmc_ops.h" #include "sd_ops.h" #include "sdio_ops.h" +static int sdio_init_func(struct mmc_card *card, unsigned int fn) +{ + struct sdio_func *func; + + BUG_ON(fn > SDIO_MAX_FUNCS); + + func = sdio_alloc_func(card); + if (IS_ERR(func)) + return PTR_ERR(func); + + func->num = fn; + + card->sdio_func[fn - 1] = func; + + return 0; +} + /* * Host is being removed. Free up the current card. */ static void mmc_sdio_remove(struct mmc_host *host) { + int i; + BUG_ON(!host); BUG_ON(!host->card); + for (i = 0;i < host->card->sdio_funcs;i++) { + if (host->card->sdio_func[i]) { + sdio_remove_func(host->card->sdio_func[i]); + host->card->sdio_func[i] = NULL; + } + } + mmc_remove_card(host->card); host->card = NULL; } @@ -73,7 +101,7 @@ static const struct mmc_bus_ops mmc_sdio_ops = { int mmc_attach_sdio(struct mmc_host *host, u32 ocr) { int err; - int funcs; + int i, funcs; struct mmc_card *card; BUG_ON(!host); @@ -132,13 +160,16 @@ int mmc_attach_sdio(struct mmc_host *host, u32 ocr) } card->type = MMC_TYPE_SDIO; + card->sdio_funcs = funcs; + + host->card = card; /* * Set card RCA. */ err = mmc_send_relative_addr(host, &card->rca); if (err) - goto free_card; + goto remove; mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); @@ -147,23 +178,46 @@ int mmc_attach_sdio(struct mmc_host *host, u32 ocr) */ err = mmc_select_card(card); if (err) - goto free_card; + goto remove; - host->card = card; + /* + * Initialize (but don't add) all present functions. + */ + for (i = 0;i < funcs;i++) { + err = sdio_init_func(host->card, i + 1); + if (err) + goto remove; + } mmc_release_host(host); + /* + * First add the card to the driver model... + */ err = mmc_add_card(host->card); if (err) - goto reclaim_host; + goto remove_added; + + /* + * ...then the SDIO functions. + */ + for (i = 0;i < funcs;i++) { + err = sdio_add_func(host->card->sdio_func[i]); + if (err) + goto remove_added; + } return 0; -reclaim_host: + +remove_added: + /* Remove without lock if the device has been added. */ + mmc_sdio_remove(host); mmc_claim_host(host); -free_card: - mmc_remove_card(card); - host->card = NULL; +remove: + /* And with lock if it hasn't been added. */ + if (host->card) + mmc_sdio_remove(host); err: mmc_detach_bus(host); mmc_release_host(host); diff --git a/drivers/mmc/core/sdio_bus.c b/drivers/mmc/core/sdio_bus.c new file mode 100644 index 00000000000..59c909e1c7c --- /dev/null +++ b/drivers/mmc/core/sdio_bus.c @@ -0,0 +1,129 @@ +/* + * linux/drivers/mmc/core/sdio_bus.c + * + * Copyright 2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * SDIO function driver model + */ + +#include +#include + +#include +#include + +#include "sdio_bus.h" + +#define dev_to_sdio_func(d) container_of(d, struct sdio_func, dev) + +/* + * This currently matches any SDIO function to any driver in order + * to help initial development and testing. + */ +static int sdio_bus_match(struct device *dev, struct device_driver *drv) +{ + return 1; +} + +static int +sdio_bus_uevent(struct device *dev, char **envp, int num_envp, char *buf, + int buf_size) +{ + envp[0] = NULL; + + return 0; +} + +static int sdio_bus_probe(struct device *dev) +{ + return -ENODEV; +} + +static int sdio_bus_remove(struct device *dev) +{ + return 0; +} + +static struct bus_type sdio_bus_type = { + .name = "sdio", + .match = sdio_bus_match, + .uevent = sdio_bus_uevent, + .probe = sdio_bus_probe, + .remove = sdio_bus_remove, +}; + +int sdio_register_bus(void) +{ + return bus_register(&sdio_bus_type); +} + +void sdio_unregister_bus(void) +{ + bus_unregister(&sdio_bus_type); +} + +static void sdio_release_func(struct device *dev) +{ + struct sdio_func *func = dev_to_sdio_func(dev); + + kfree(func); +} + +/* + * Allocate and initialise a new SDIO function structure. + */ +struct sdio_func *sdio_alloc_func(struct mmc_card *card) +{ + struct sdio_func *func; + + func = kmalloc(sizeof(struct sdio_func), GFP_KERNEL); + if (!func) + return ERR_PTR(-ENOMEM); + + memset(func, 0, sizeof(struct sdio_func)); + + func->card = card; + + device_initialize(&func->dev); + + func->dev.parent = &card->dev; + func->dev.bus = &sdio_bus_type; + func->dev.release = sdio_release_func; + + return func; +} + +/* + * Register a new SDIO function with the driver model. + */ +int sdio_add_func(struct sdio_func *func) +{ + int ret; + + snprintf(func->dev.bus_id, sizeof(func->dev.bus_id), + "%s:%d", mmc_card_id(func->card), func->num); + + ret = device_add(&func->dev); + if (ret == 0) + sdio_func_set_present(func); + + return ret; +} + +/* + * Unregister a SDIO function with the driver model, and + * (eventually) free it. + */ +void sdio_remove_func(struct sdio_func *func) +{ + if (sdio_func_present(func)) + device_del(&func->dev); + + put_device(&func->dev); +} + diff --git a/drivers/mmc/core/sdio_bus.h b/drivers/mmc/core/sdio_bus.h new file mode 100644 index 00000000000..567a76821ba --- /dev/null +++ b/drivers/mmc/core/sdio_bus.h @@ -0,0 +1,22 @@ +/* + * linux/drivers/mmc/core/sdio_bus.h + * + * Copyright 2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ +#ifndef _MMC_CORE_SDIO_BUS_H +#define _MMC_CORE_SDIO_BUS_H + +struct sdio_func *sdio_alloc_func(struct mmc_card *card); +int sdio_add_func(struct sdio_func *func); +void sdio_remove_func(struct sdio_func *func); + +int sdio_register_bus(void); +void sdio_unregister_bus(void); + +#endif + diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 43480ebebf9..9f5f74482d9 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -56,6 +56,9 @@ struct sd_switch_caps { }; struct mmc_host; +struct sdio_func; + +#define SDIO_MAX_FUNCS 7 /* * MMC device @@ -73,6 +76,7 @@ struct mmc_card { #define MMC_STATE_READONLY (1<<1) /* card is read-only */ #define MMC_STATE_HIGHSPEED (1<<2) /* card is in high speed mode */ #define MMC_STATE_BLOCKADDR (1<<3) /* card uses block-addressing */ + u32 raw_cid[4]; /* raw card CID */ u32 raw_csd[4]; /* raw card CSD */ u32 raw_scr[2]; /* raw card SCR */ @@ -81,6 +85,9 @@ struct mmc_card { struct mmc_ext_csd ext_csd; /* mmc v4 extended card specific */ struct sd_scr scr; /* extra SD information */ struct sd_switch_caps sw_caps; /* switch (CMD6) caps */ + + unsigned int sdio_funcs; /* number of SDIO functions */ + struct sdio_func *sdio_func[SDIO_MAX_FUNCS]; /* SDIO functions (devices) */ }; #define mmc_card_mmc(c) ((c)->type == MMC_TYPE_MMC) diff --git a/include/linux/mmc/sdio_func.h b/include/linux/mmc/sdio_func.h new file mode 100644 index 00000000000..50c78db420e --- /dev/null +++ b/include/linux/mmc/sdio_func.h @@ -0,0 +1,35 @@ +/* + * include/linux/mmc/sdio_func.h + * + * Copyright 2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#ifndef MMC_SDIO_FUNC_H +#define MMC_SDIO_FUNC_H + +struct mmc_card; + +/* + * SDIO function devices + */ +struct sdio_func { + struct mmc_card *card; /* the card this device belongs to */ + struct device dev; /* the device */ + unsigned int num; /* function number */ + unsigned int state; /* function state */ +#define SDIO_STATE_PRESENT (1<<0) /* present in sysfs */ +}; + +#define sdio_func_present(f) ((f)->state & SDIO_STATE_PRESENT) + +#define sdio_func_set_present(f) ((f)->state |= SDIO_STATE_PRESENT) + +#define sdio_func_id(f) ((f)->dev.bus_id) + +#endif + -- cgit v1.2.3 From f76c85154d320497bf1a939a98d6c432edcbd4a9 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sun, 27 May 2007 12:00:02 +0200 Subject: mmc: add SDIO driver handling Add basic driver handling to the SDIO device model. Signed-off-by: Pierre Ossman --- drivers/mmc/core/sdio_bus.c | 23 +++++++++++++++++++++++ include/linux/mmc/sdio_func.h | 18 ++++++++++++++++++ 2 files changed, 41 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/core/sdio_bus.c b/drivers/mmc/core/sdio_bus.c index 59c909e1c7c..fa488cea859 100644 --- a/drivers/mmc/core/sdio_bus.c +++ b/drivers/mmc/core/sdio_bus.c @@ -67,6 +67,29 @@ void sdio_unregister_bus(void) bus_unregister(&sdio_bus_type); } +/** + * sdio_register_driver - register a function driver + * @drv: SDIO function driver + */ +int sdio_register_driver(struct sdio_driver *drv) +{ + drv->drv.name = drv->name; + drv->drv.bus = &sdio_bus_type; + return driver_register(&drv->drv); +} +EXPORT_SYMBOL_GPL(sdio_register_driver); + +/** + * sdio_unregister_driver - unregister a function driver + * @drv: SDIO function driver + */ +void sdio_unregister_driver(struct sdio_driver *drv) +{ + drv->drv.bus = &sdio_bus_type; + driver_unregister(&drv->drv); +} +EXPORT_SYMBOL_GPL(sdio_unregister_driver); + static void sdio_release_func(struct device *dev) { struct sdio_func *func = dev_to_sdio_func(dev); diff --git a/include/linux/mmc/sdio_func.h b/include/linux/mmc/sdio_func.h index 50c78db420e..13a1a9ca4b6 100644 --- a/include/linux/mmc/sdio_func.h +++ b/include/linux/mmc/sdio_func.h @@ -31,5 +31,23 @@ struct sdio_func { #define sdio_func_id(f) ((f)->dev.bus_id) +#define sdio_get_drvdata(f) dev_get_drvdata(&(f)->dev) +#define sdio_set_drvdata(f,d) dev_set_drvdata(&(f)->dev, d) + +/* + * SDIO function device driver + */ +struct sdio_driver { + char *name; + + int (*probe)(struct sdio_func *); + void (*remove)(struct sdio_func *); + + struct device_driver drv; +}; + +extern int sdio_register_driver(struct sdio_driver *); +extern void sdio_unregister_driver(struct sdio_driver *); + #endif -- cgit v1.2.3 From 46f555f2731a14545a09ec06d27bd18e8e07069f Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sun, 27 May 2007 12:57:15 +0200 Subject: mmc: add basic SDIO I/O operations Add command wrappers that simplify register access from SDIO function drivers. Signed-off-by: Pierre Ossman --- drivers/mmc/core/Makefile | 3 +- drivers/mmc/core/sdio_io.c | 105 ++++++++++++++++++++++++++++++++++++++++++ include/linux/mmc/sdio_func.h | 12 +++++ 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 drivers/mmc/core/sdio_io.c (limited to 'drivers') diff --git a/drivers/mmc/core/Makefile b/drivers/mmc/core/Makefile index 71ab3d1e1eb..bf7a0024803 100644 --- a/drivers/mmc/core/Makefile +++ b/drivers/mmc/core/Makefile @@ -9,5 +9,6 @@ endif obj-$(CONFIG_MMC) += mmc_core.o mmc_core-y := core.o sysfs.o bus.o host.o \ mmc.o mmc_ops.o sd.o sd_ops.o \ - sdio.o sdio_ops.o sdio_bus.o + sdio.o sdio_ops.o sdio_bus.o \ + sdio_io.o diff --git a/drivers/mmc/core/sdio_io.c b/drivers/mmc/core/sdio_io.c new file mode 100644 index 00000000000..4ad06e57563 --- /dev/null +++ b/drivers/mmc/core/sdio_io.c @@ -0,0 +1,105 @@ +/* + * linux/drivers/mmc/core/sdio_io.c + * + * Copyright 2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#include +#include +#include + +#include "sdio_ops.h" + +/** + * sdio_claim_host - exclusively claim a bus for a certain SDIO function + * @func: SDIO function that will be accessed + * + * Claim a bus for a set of operations. The SDIO function given + * is used to figure out which bus is relevant. + */ +void sdio_claim_host(struct sdio_func *func) +{ + BUG_ON(!func); + BUG_ON(!func->card); + + mmc_claim_host(func->card->host); +} +EXPORT_SYMBOL_GPL(sdio_claim_host); + +/** + * sdio_release_host - release a bus for a certain SDIO function + * @func: SDIO function that was accessed + * + * Release a bus, allowing others to claim the bus for their + * operations. + */ +void sdio_release_host(struct sdio_func *func) +{ + BUG_ON(!func); + BUG_ON(!func->card); + + mmc_release_host(func->card->host); +} +EXPORT_SYMBOL_GPL(sdio_release_host); + +/** + * sdio_readb - read a single byte from a SDIO function + * @func: SDIO function to access + * @addr: address to read + * @err_ret: optional status value from transfer + * + * Reads a single byte from the address space of a given SDIO + * function. If there is a problem reading the address, 0xff + * is returned and @err_ret will contain the error code. + */ +unsigned char sdio_readb(struct sdio_func *func, unsigned int addr, + int *err_ret) +{ + int ret; + unsigned char val; + + BUG_ON(!func); + + if (err_ret) + *err_ret = 0; + + ret = mmc_io_rw_direct(func->card, 0, func->num, addr, 0, &val); + if (ret) { + if (err_ret) + *err_ret = ret; + return 0xFF; + } + + return val; +} +EXPORT_SYMBOL_GPL(sdio_readb); + +/** + * sdio_writeb - write a single byte to a SDIO function + * @func: SDIO function to access + * @b: byte to write + * @addr: address to write to + * @err_ret: optional status value from transfer + * + * Writes a single byte to the address space of a given SDIO + * function. @err_ret will contain the status of the actual + * transfer. + */ +void sdio_writeb(struct sdio_func *func, unsigned char b, unsigned int addr, + int *err_ret) +{ + int ret; + + BUG_ON(!func); + + ret = mmc_io_rw_direct(func->card, 1, func->num, addr, b, NULL); + if (err_ret) + *err_ret = ret; +} +EXPORT_SYMBOL_GPL(sdio_writeb); + diff --git a/include/linux/mmc/sdio_func.h b/include/linux/mmc/sdio_func.h index 13a1a9ca4b6..5c56df19628 100644 --- a/include/linux/mmc/sdio_func.h +++ b/include/linux/mmc/sdio_func.h @@ -49,5 +49,17 @@ struct sdio_driver { extern int sdio_register_driver(struct sdio_driver *); extern void sdio_unregister_driver(struct sdio_driver *); +/* + * SDIO I/O operations + */ +extern void sdio_claim_host(struct sdio_func *func); +extern void sdio_release_host(struct sdio_func *func); + +extern unsigned char sdio_readb(struct sdio_func *func, + unsigned int addr, int *err_ret); + +extern void sdio_writeb(struct sdio_func *func, unsigned char b, + unsigned int addr, int *err_ret); + #endif -- cgit v1.2.3 From fa64efa1f2a0672767ad0753a6e4bfa4bcc77b87 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sun, 27 May 2007 14:22:37 +0200 Subject: mmc: enable/disable functions for SDIO Like many other buses, the devices (functions) on the SDIO bus must be enabled before they can be used. Add functions that allow drivers to do so. Signed-off-by: Pierre Ossman --- drivers/mmc/core/sdio_io.c | 93 +++++++++++++++++++++++++++++++++++++++++++ include/linux/mmc/sdio.h | 92 ++++++++++++++++++++++++++++++++++++++++++ include/linux/mmc/sdio_func.h | 3 ++ 3 files changed, 188 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/core/sdio_io.c b/drivers/mmc/core/sdio_io.c index 4ad06e57563..eb6c20935ce 100644 --- a/drivers/mmc/core/sdio_io.c +++ b/drivers/mmc/core/sdio_io.c @@ -11,6 +11,7 @@ #include #include +#include #include #include "sdio_ops.h" @@ -47,6 +48,98 @@ void sdio_release_host(struct sdio_func *func) } EXPORT_SYMBOL_GPL(sdio_release_host); +/** + * sdio_enable_func - enables a SDIO function for usage + * @func: SDIO function to enable + * + * Powers up and activates a SDIO function so that register + * access is possible. + */ +int sdio_enable_func(struct sdio_func *func) +{ + int ret; + unsigned char reg; + unsigned long timeout; + + BUG_ON(!func); + BUG_ON(!func->card); + + pr_debug("SDIO: Enabling device %s...\n", sdio_func_id(func)); + + ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IOEx, 0, ®); + if (ret) + goto err; + + reg |= 1 << func->num; + + ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IOEx, reg, NULL); + if (ret) + goto err; + + /* + * FIXME: This should timeout based on information in the CIS, + * but we don't have card to parse that yet. + */ + timeout = jiffies + HZ; + + while (1) { + ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IORx, 0, ®); + if (ret) + goto err; + if (reg & (1 << func->num)) + break; + ret = -ETIME; + if (time_after(jiffies, timeout)) + goto err; + } + + pr_debug("SDIO: Enabled device %s\n", sdio_func_id(func)); + + return 0; + +err: + pr_debug("SDIO: Failed to enable device %s\n", sdio_func_id(func)); + return ret; +} +EXPORT_SYMBOL_GPL(sdio_enable_func); + +/** + * sdio_disable_func - disable a SDIO function + * @func: SDIO function to disable + * + * Powers down and deactivates a SDIO function. Register access + * to this function will fail until the function is reenabled. + */ +int sdio_disable_func(struct sdio_func *func) +{ + int ret; + unsigned char reg; + + BUG_ON(!func); + BUG_ON(!func->card); + + pr_debug("SDIO: Disabling device %s...\n", sdio_func_id(func)); + + ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IOEx, 0, ®); + if (ret) + goto err; + + reg &= ~(1 << func->num); + + ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IOEx, reg, NULL); + if (ret) + goto err; + + pr_debug("SDIO: Disabled device %s\n", sdio_func_id(func)); + + return 0; + +err: + pr_debug("SDIO: Failed to disable device %s\n", sdio_func_id(func)); + return -EIO; +} +EXPORT_SYMBOL_GPL(sdio_disable_func); + /** * sdio_readb - read a single byte from a SDIO function * @func: SDIO function to access diff --git a/include/linux/mmc/sdio.h b/include/linux/mmc/sdio.h index e5f06de7d52..56239f4aab0 100644 --- a/include/linux/mmc/sdio.h +++ b/include/linux/mmc/sdio.h @@ -49,5 +49,97 @@ #define R5_STATUS(x) (x & 0xCB00) #define R5_IO_CURRENT_STATE(x) ((x & 0x3000) >> 12) /* s, b */ +/* + * Card Common Control Registers (CCCR) + */ + +#define SDIO_CCCR_CCCR 0x00 + +#define SDIO_CCCR_REV_1_00 0 /* CCCR/FBR Version 1.00 */ +#define SDIO_CCCR_REV_1_10 1 /* CCCR/FBR Version 1.10 */ +#define SDIO_CCCR_REV_1_20 2 /* CCCR/FBR Version 1.20 */ + +#define SDIO_SDIO_REV_1_00 0 /* SDIO Spec Version 1.00 */ +#define SDIO_SDIO_REV_1_10 1 /* SDIO Spec Version 1.10 */ +#define SDIO_SDIO_REV_1_20 2 /* SDIO Spec Version 1.20 */ +#define SDIO_SDIO_REV_2_00 3 /* SDIO Spec Version 2.00 */ + +#define SDIO_CCCR_SD 0x01 + +#define SDIO_SD_REV_1_01 0 /* SD Physical Spec Version 1.01 */ +#define SDIO_SD_REV_1_10 1 /* SD Physical Spec Version 1.10 */ +#define SDIO_SD_REV_2_00 2 /* SD Physical Spec Version 2.00 */ + +#define SDIO_CCCR_IOEx 0x02 +#define SDIO_CCCR_IORx 0x03 + +#define SDIO_CCCR_IENx 0x04 /* Function/Master Interrupt Enable */ +#define SDIO_CCCR_INTx 0x05 /* Function Interrupt Pending */ + +#define SDIO_CCCR_ABORT 0x06 /* function abort/card reset */ + +#define SDIO_CCCR_IF 0x07 /* bus interface controls */ + +#define SDIO_BUS_WIDTH_1BIT 0x00 +#define SDIO_BUS_WIDTH_4BIT 0x02 + +#define SDIO_BUS_CD_DISABLE 0x80 /* disable pull-up on DAT3 (pin 1) */ + +#define SDIO_CCCR_CAPS 0x08 + +#define SDIO_CCCR_CAP_SDC 0x01 /* can do CMD52 while data transfer */ +#define SDIO_CCCR_CAP_SMB 0x02 /* can do multi-block xfers (CMD53) */ +#define SDIO_CCCR_CAP_SRW 0x04 /* supports read-wait protocol */ +#define SDIO_CCCR_CAP_SBS 0x08 /* supports suspend/resume */ +#define SDIO_CCCR_CAP_S4MI 0x10 /* interrupt during 4-bit CMD53 */ +#define SDIO_CCCR_CAP_E4MI 0x20 /* enable ints during 4-bit CMD53 */ +#define SDIO_CCCR_CAP_LSC 0x40 /* low speed card */ +#define SDIO_CCCR_CAP_4BLS 0x80 /* 4 bit low speed card */ + +#define SDIO_CCCR_CIS 0x09 /* common CIS pointer (3 bytes) */ + +/* Following 4 regs are valid only if SBS is set */ +#define SDIO_CCCR_SUSPEND 0x0c +#define SDIO_CCCR_SELx 0x0d +#define SDIO_CCCR_EXECx 0x0e +#define SDIO_CCCR_READYx 0x0f + +#define SDIO_CCCR_BLKSIZE 0x10 + +#define SDIO_CCCR_POWER 0x12 + +#define SDIO_POWER_SMPC 0x01 /* Supports Master Power Control */ +#define SDIO_POWER_EMPC 0x02 /* Enable Master Power Control */ + +#define SDIO_CCCR_SPEED 0x13 + +#define SDIO_SPEED_SHS 0x01 /* Supports High-Speed mode */ +#define SDIO_SPEED_EHS 0x02 /* Enable High-Speed mode */ + +/* + * Function Basic Registers (FBR) + */ + +#define SDIO_FBR_STD_IF 0x00 + +#define SDIO_FBR_SUPPORTS_CSA 0x40 /* supports Code Storage Area */ +#define SDIO_FBR_ENABLE_CSA 0x80 /* enable Code Storage Area */ + +#define SDIO_FBR_STD_IF_EXT 0x01 + +#define SDIO_FBR_POWER 0x02 + +#define SDIO_FBR_POWER_SPS 0x01 /* Supports Power Selection */ +#define SDIO_FBR_POWER_EPS 0x02 /* Enable (low) Power Selection */ + +#define SDIO_FBR_CIS 0x09 /* CIS pointer (3 bytes) */ + + +#define SDIO_FBR_CSA 0x0C /* CSA pointer (3 bytes) */ + +#define SDIO_FBR_CSA_DATA 0x0F + +#define SDIO_FBR_BLKSIZE 0x10 /* block size (2 bytes) */ + #endif diff --git a/include/linux/mmc/sdio_func.h b/include/linux/mmc/sdio_func.h index 5c56df19628..3365fef5f71 100644 --- a/include/linux/mmc/sdio_func.h +++ b/include/linux/mmc/sdio_func.h @@ -55,6 +55,9 @@ extern void sdio_unregister_driver(struct sdio_driver *); extern void sdio_claim_host(struct sdio_func *func); extern void sdio_release_host(struct sdio_func *func); +extern int sdio_enable_func(struct sdio_func *func); +extern int sdio_disable_func(struct sdio_func *func); + extern unsigned char sdio_readb(struct sdio_func *func, unsigned int addr, int *err_ret); -- cgit v1.2.3 From 35c66c19088bddb11110c124bad8abd4441a8421 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 11 Jun 2007 20:25:43 +0200 Subject: sdio: read and decode interesting parts of the CCCR Signed-off-by: Pierre Ossman --- drivers/mmc/core/sdio.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/mmc/card.h | 11 +++++++++ 2 files changed, 74 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 444328581ce..7ce3e3104d2 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -13,6 +13,7 @@ #include #include +#include #include #include "core.h" @@ -39,6 +40,61 @@ static int sdio_init_func(struct mmc_card *card, unsigned int fn) return 0; } +static int sdio_read_cccr(struct mmc_card *card) +{ + int ret; + int cccr_vsn; + unsigned char data; + + memset(&card->cccr, 0, sizeof(struct sdio_cccr)); + + ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_CCCR, 0, &data); + if (ret) + goto out; + + cccr_vsn = data & 0x0f; + + if (cccr_vsn > SDIO_CCCR_REV_1_20) { + printk(KERN_ERR "%s: unrecognised CCCR structure version %d\n", + mmc_hostname(card->host), cccr_vsn); + return -EINVAL; + } + + card->cccr.sdio_vsn = (data & 0xf0) >> 4; + + ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_CAPS, 0, &data); + if (ret) + goto out; + + if (data & SDIO_CCCR_CAP_SMB) + card->cccr.multi_block = 1; + if (data & SDIO_CCCR_CAP_LSC) + card->cccr.low_speed = 1; + if (data & SDIO_CCCR_CAP_4BLS) + card->cccr.wide_bus = 1; + + if (cccr_vsn >= SDIO_CCCR_REV_1_10) { + ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_POWER, 0, &data); + if (ret) + goto out; + + if (data & SDIO_POWER_SMPC) + card->cccr.high_power = 1; + } + + if (cccr_vsn >= SDIO_CCCR_REV_1_20) { + ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_SPEED, 0, &data); + if (ret) + goto out; + + if (data & SDIO_SPEED_SHS) + card->cccr.high_speed = 1; + } + +out: + return ret; +} + /* * Host is being removed. Free up the current card. */ @@ -180,6 +236,13 @@ int mmc_attach_sdio(struct mmc_host *host, u32 ocr) if (err) goto remove; + /* + * Read the common registers. + */ + err = sdio_read_cccr(card); + if (err) + goto remove; + /* * Initialize (but don't add) all present functions. */ diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 9f5f74482d9..520d9d29b3b 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -55,6 +55,16 @@ struct sd_switch_caps { unsigned int hs_max_dtr; }; +struct sdio_cccr { + unsigned int sdio_vsn; + unsigned int sd_vsn; + unsigned int multi_block:1, + low_speed:1, + wide_bus:1, + high_power:1, + high_speed:1; +}; + struct mmc_host; struct sdio_func; @@ -87,6 +97,7 @@ struct mmc_card { struct sd_switch_caps sw_caps; /* switch (CMD6) caps */ unsigned int sdio_funcs; /* number of SDIO functions */ + struct sdio_cccr cccr; /* common card info */ struct sdio_func *sdio_func[SDIO_MAX_FUNCS]; /* SDIO functions (devices) */ }; -- cgit v1.2.3 From 0597007f1b22bbb5d4234ca09c045f9bb2711270 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 11 Jun 2007 21:01:00 +0200 Subject: sdio: basic parsing of FBR Signed-off-by: Pierre Ossman --- drivers/mmc/core/sdio.c | 38 ++++++++++++++++++++++++++++++++++++++ include/linux/mmc/sdio_func.h | 5 +++++ 2 files changed, 43 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 7ce3e3104d2..be623856f28 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -23,8 +23,34 @@ #include "sd_ops.h" #include "sdio_ops.h" +static int sdio_read_fbr(struct sdio_func *func) +{ + int ret; + unsigned char data; + + ret = mmc_io_rw_direct(func->card, 0, 0, + func->num * 0x100 + SDIO_FBR_STD_IF, 0, &data); + if (ret) + goto out; + + data &= 0x0f; + + if (data == 0x0f) { + ret = mmc_io_rw_direct(func->card, 0, 0, + func->num * 0x100 + SDIO_FBR_STD_IF_EXT, 0, &data); + if (ret) + goto out; + } + + func->class = data; + +out: + return ret; +} + static int sdio_init_func(struct mmc_card *card, unsigned int fn) { + int ret; struct sdio_func *func; BUG_ON(fn > SDIO_MAX_FUNCS); @@ -35,9 +61,21 @@ static int sdio_init_func(struct mmc_card *card, unsigned int fn) func->num = fn; + ret = sdio_read_fbr(func); + if (ret) + goto fail; + card->sdio_func[fn - 1] = func; return 0; + +fail: + /* + * It is okay to remove the function here even though we hold + * the host lock as we haven't registered the device yet. + */ + sdio_remove_func(func); + return ret; } static int sdio_read_cccr(struct mmc_card *card) diff --git a/include/linux/mmc/sdio_func.h b/include/linux/mmc/sdio_func.h index 3365fef5f71..4164809a8e6 100644 --- a/include/linux/mmc/sdio_func.h +++ b/include/linux/mmc/sdio_func.h @@ -21,6 +21,11 @@ struct sdio_func { struct mmc_card *card; /* the card this device belongs to */ struct device dev; /* the device */ unsigned int num; /* function number */ + + unsigned char class; /* standard interface class */ + unsigned short vendor; /* vendor id */ + unsigned short device; /* device id */ + unsigned int state; /* function state */ #define SDIO_STATE_PRESENT (1<<0) /* present in sysfs */ }; -- cgit v1.2.3 From b726126196d54cbbba0924191e5c4dd5ba747fa2 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Sat, 16 Jun 2007 02:04:16 -0400 Subject: sdio: initial CIS parsing code Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman --- drivers/mmc/core/Makefile | 2 +- drivers/mmc/core/sdio.c | 5 ++ drivers/mmc/core/sdio_cis.c | 119 ++++++++++++++++++++++++++++++++++++++++++++ drivers/mmc/core/sdio_cis.h | 19 +++++++ 4 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 drivers/mmc/core/sdio_cis.c create mode 100644 drivers/mmc/core/sdio_cis.h (limited to 'drivers') diff --git a/drivers/mmc/core/Makefile b/drivers/mmc/core/Makefile index bf7a0024803..05d69fc72c1 100644 --- a/drivers/mmc/core/Makefile +++ b/drivers/mmc/core/Makefile @@ -10,5 +10,5 @@ obj-$(CONFIG_MMC) += mmc_core.o mmc_core-y := core.o sysfs.o bus.o host.o \ mmc.o mmc_ops.o sd.o sd_ops.o \ sdio.o sdio_ops.o sdio_bus.o \ - sdio_io.o + sdio_cis.o sdio_io.o diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index be623856f28..c5baf76146b 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -22,6 +22,7 @@ #include "mmc_ops.h" #include "sd_ops.h" #include "sdio_ops.h" +#include "sdio_cis.h" static int sdio_read_fbr(struct sdio_func *func) { @@ -65,6 +66,10 @@ static int sdio_init_func(struct mmc_card *card, unsigned int fn) if (ret) goto fail; + ret = sdio_read_cis(func); + if (ret) + goto fail; + card->sdio_func[fn - 1] = func; return 0; diff --git a/drivers/mmc/core/sdio_cis.c b/drivers/mmc/core/sdio_cis.c new file mode 100644 index 00000000000..114b600cd78 --- /dev/null +++ b/drivers/mmc/core/sdio_cis.c @@ -0,0 +1,119 @@ +/* + * linux/drivers/mmc/core/sdio_cis.c + * + * Author: Nicolas Pitre + * Created: June 11, 2007 + * Copyright: MontaVista Software Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#include + +#include +#include +#include + +#include "sdio_cis.h" +#include "sdio_ops.h" + +static int cistpl_manfid(struct sdio_func *func, + const unsigned char *buf, + unsigned size) +{ + /* TPLMID_MANF */ + func->vendor = buf[0] | (buf[1] << 8); + + /* TPLMID_CARD */ + func->device = buf[2] | (buf[3] << 8); + + return 0; +} + +struct cis_tpl { + unsigned char code; + unsigned char min_size; + int (*parse)(struct sdio_func *, const unsigned char *buf, unsigned size); +}; + +static const struct cis_tpl cis_tpl_list[] = { + { 0x15, 3, /* cistpl_vers_1 */ }, + { 0x20, 4, cistpl_manfid }, + { 0x21, 2, /* cistpl_funcid */ }, + { 0x22, 0, /* cistpl_funce */ }, +}; + +int sdio_read_cis(struct sdio_func *func) +{ + int ret; + unsigned char *buf; + unsigned i, ptr = 0; + + for (i = 0; i < 3; i++) { + unsigned char x; + ret = mmc_io_rw_direct(func->card, 0, 0, + func->num * 0x100 + SDIO_FBR_CIS + i, 0, &x); + if (ret) + return ret; + ptr |= x << (i * 8); + } + + buf = kmalloc(256, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + do { + unsigned char tpl_code, tpl_link; + const struct cis_tpl *tpl; + + ret = mmc_io_rw_direct(func->card, 0, 0, ptr++, 0, &tpl_code); + if (ret) + break; + + /* 0xff means we're done */ + if (tpl_code == 0xff) + break; + + ret = mmc_io_rw_direct(func->card, 0, 0, ptr++, 0, &tpl_link); + if (ret) + break; + + for (i = 0; i < ARRAY_SIZE(cis_tpl_list); i++) + if (cis_tpl_list[i].code == tpl_code) + break; + if (i >= ARRAY_SIZE(cis_tpl_list)) { + printk(KERN_WARNING + "%s: unknown CIS tuple 0x%02x of length %u\n", + sdio_func_id(func), tpl_code, tpl_link); + ptr += tpl_link; + continue; + } + tpl = cis_tpl_list + i; + + if (tpl_link < tpl->min_size) { + printk(KERN_ERR + "%s: bad CIS tuple 0x%02x (length = %u, expected >= %u\n", + sdio_func_id(func), tpl_code, tpl_link, tpl->min_size); + ret = -EINVAL; + break; + } + + for (i = 0; i < tpl_link; i++) { + ret = mmc_io_rw_direct(func->card, 0, 0, ptr + i, 0, &buf[i]); + if (ret) + break; + } + if (ret) + break; + ptr += tpl_link; + + if (tpl->parse) + ret = tpl->parse(func, buf, tpl_link); + } while (!ret); + + kfree(buf); + return ret; +} diff --git a/drivers/mmc/core/sdio_cis.h b/drivers/mmc/core/sdio_cis.h new file mode 100644 index 00000000000..df21c495d13 --- /dev/null +++ b/drivers/mmc/core/sdio_cis.h @@ -0,0 +1,19 @@ +/* + * linux/drivers/mmc/core/sdio_cis.h + * + * Author: Nicolas Pitre + * Created: June 11, 2007 + * Copyright: MontaVista Software Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#ifndef _MMC_SDIO_CIS_H +#define _MMC_SDIO_CIS_H + +int sdio_read_cis(struct sdio_func *func); + +#endif -- cgit v1.2.3 From b1538bcf75e2e11459947ec4d4329ed04fbe2b2c Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Sat, 16 Jun 2007 02:06:47 -0400 Subject: sdio: link unknown CIS tuples to the sdio_func structure This way those tuples that the core cares about are consumed by the core code, and tuples that only function drivers might make sense of are available to drivers. Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman --- drivers/mmc/core/sdio_bus.c | 3 ++ drivers/mmc/core/sdio_cis.c | 83 ++++++++++++++++++++++++++++--------------- drivers/mmc/core/sdio_cis.h | 1 + include/linux/mmc/sdio_func.h | 12 +++++++ 4 files changed, 70 insertions(+), 29 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/core/sdio_bus.c b/drivers/mmc/core/sdio_bus.c index fa488cea859..78e0381f55a 100644 --- a/drivers/mmc/core/sdio_bus.c +++ b/drivers/mmc/core/sdio_bus.c @@ -17,6 +17,7 @@ #include #include +#include "sdio_cis.h" #include "sdio_bus.h" #define dev_to_sdio_func(d) container_of(d, struct sdio_func, dev) @@ -94,6 +95,8 @@ static void sdio_release_func(struct device *dev) { struct sdio_func *func = dev_to_sdio_func(dev); + sdio_free_cis(func); + kfree(func); } diff --git a/drivers/mmc/core/sdio_cis.c b/drivers/mmc/core/sdio_cis.c index 114b600cd78..b6c7342572c 100644 --- a/drivers/mmc/core/sdio_cis.c +++ b/drivers/mmc/core/sdio_cis.c @@ -5,6 +5,8 @@ * Created: June 11, 2007 * Copyright: MontaVista Software Inc. * + * Copyright 2007 Pierre Ossman + * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at @@ -49,7 +51,7 @@ static const struct cis_tpl cis_tpl_list[] = { int sdio_read_cis(struct sdio_func *func) { int ret; - unsigned char *buf; + struct sdio_func_tuple *this, **prev; unsigned i, ptr = 0; for (i = 0; i < 3; i++) { @@ -61,13 +63,11 @@ int sdio_read_cis(struct sdio_func *func) ptr |= x << (i * 8); } - buf = kmalloc(256, GFP_KERNEL); - if (!buf) - return -ENOMEM; + /* find the list tail */ + for (prev = &func->tuples; *prev; prev = &(*prev)->next); do { unsigned char tpl_code, tpl_link; - const struct cis_tpl *tpl; ret = mmc_io_rw_direct(func->card, 0, 0, ptr++, 0, &tpl_code); if (ret) @@ -81,39 +81,64 @@ int sdio_read_cis(struct sdio_func *func) if (ret) break; - for (i = 0; i < ARRAY_SIZE(cis_tpl_list); i++) - if (cis_tpl_list[i].code == tpl_code) + this = kmalloc(sizeof(*this) + tpl_link, GFP_KERNEL); + if (!this) + return -ENOMEM; + + for (i = 0; i < tpl_link; i++) { + ret = mmc_io_rw_direct(func->card, 0, 0, + ptr + i, 0, &this->data[i]); + if (ret) break; - if (i >= ARRAY_SIZE(cis_tpl_list)) { - printk(KERN_WARNING - "%s: unknown CIS tuple 0x%02x of length %u\n", - sdio_func_id(func), tpl_code, tpl_link); - ptr += tpl_link; - continue; } - tpl = cis_tpl_list + i; - - if (tpl_link < tpl->min_size) { - printk(KERN_ERR - "%s: bad CIS tuple 0x%02x (length = %u, expected >= %u\n", - sdio_func_id(func), tpl_code, tpl_link, tpl->min_size); - ret = -EINVAL; + if (ret) { + kfree(this); break; } - for (i = 0; i < tpl_link; i++) { - ret = mmc_io_rw_direct(func->card, 0, 0, ptr + i, 0, &buf[i]); - if (ret) + for (i = 0; i < ARRAY_SIZE(cis_tpl_list); i++) + if (cis_tpl_list[i].code == tpl_code) break; + if (i >= ARRAY_SIZE(cis_tpl_list)) { + /* this tuple is unknown to the core */ + this->next = NULL; + this->code = tpl_code; + this->size = tpl_link; + *prev = this; + prev = &this->next; + printk(KERN_DEBUG + "%s: queuing CIS tuple 0x%02x length %u\n", + sdio_func_id(func), tpl_code, tpl_link); + } else { + const struct cis_tpl *tpl = cis_tpl_list + i; + if (tpl_link < tpl->min_size) { + printk(KERN_ERR + "%s: bad CIS tuple 0x%02x (length = %u, expected >= %u)\n", + sdio_func_id(func), tpl_code, tpl_link, tpl->min_size); + ret = -EINVAL; + } else if (tpl->parse) + ret = tpl->parse(func, this->data, tpl_link); + kfree(this); } - if (ret) - break; - ptr += tpl_link; - if (tpl->parse) - ret = tpl->parse(func, buf, tpl_link); + ptr += tpl_link; } while (!ret); - kfree(buf); return ret; } + +void sdio_free_cis(struct sdio_func *func) +{ + struct sdio_func_tuple *tuple, *victim; + + tuple = func->tuples; + + while (tuple) { + victim = tuple; + tuple = tuple->next; + kfree(victim); + } + + func->tuples = NULL; +} + diff --git a/drivers/mmc/core/sdio_cis.h b/drivers/mmc/core/sdio_cis.h index df21c495d13..863d3d51637 100644 --- a/drivers/mmc/core/sdio_cis.h +++ b/drivers/mmc/core/sdio_cis.h @@ -15,5 +15,6 @@ #define _MMC_SDIO_CIS_H int sdio_read_cis(struct sdio_func *func); +void sdio_free_cis(struct sdio_func *func); #endif diff --git a/include/linux/mmc/sdio_func.h b/include/linux/mmc/sdio_func.h index 4164809a8e6..269067663c8 100644 --- a/include/linux/mmc/sdio_func.h +++ b/include/linux/mmc/sdio_func.h @@ -14,6 +14,16 @@ struct mmc_card; +/* + * SDIO function CIS tuple (unknown to the core) + */ +struct sdio_func_tuple { + struct sdio_func_tuple *next; + unsigned char code; + unsigned char size; + unsigned char data[0]; +}; + /* * SDIO function devices */ @@ -28,6 +38,8 @@ struct sdio_func { unsigned int state; /* function state */ #define SDIO_STATE_PRESENT (1<<0) /* present in sysfs */ + + struct sdio_func_tuple *tuples; }; #define sdio_func_present(f) ((f)->state & SDIO_STATE_PRESENT) -- cgit v1.2.3 From 1a632f8cdc33e7f8edca352164f0c96a75d08f08 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 30 Jul 2007 15:15:30 +0200 Subject: sdio: split up common and function CIS parsing Add a more clean separation between global, common CIS information and the function specific one as we need the common information in places where no specific function is specified. Signed-off-by: Pierre Ossman --- drivers/mmc/core/bus.c | 3 + drivers/mmc/core/sdio.c | 9 +- drivers/mmc/core/sdio_bus.c | 2 +- drivers/mmc/core/sdio_cis.c | 198 +++++++++++++++++++++++++++++++++++++----- drivers/mmc/core/sdio_cis.h | 7 +- include/linux/mmc/card.h | 10 +++ include/linux/mmc/sdio_func.h | 2 + 7 files changed, 205 insertions(+), 26 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index 87a6070522f..9be11ec05d8 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -19,6 +19,7 @@ #include "sysfs.h" #include "core.h" +#include "sdio_cis.h" #include "bus.h" #define dev_to_mmc_card(d) container_of(d, struct mmc_card, dev) @@ -181,6 +182,8 @@ static void mmc_release_card(struct device *dev) { struct mmc_card *card = dev_to_mmc_card(dev); + sdio_free_common_cis(card); + kfree(card); } diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index c5baf76146b..1fb36a34046 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -66,7 +66,7 @@ static int sdio_init_func(struct mmc_card *card, unsigned int fn) if (ret) goto fail; - ret = sdio_read_cis(func); + ret = sdio_read_func_cis(func); if (ret) goto fail; @@ -286,6 +286,13 @@ int mmc_attach_sdio(struct mmc_host *host, u32 ocr) if (err) goto remove; + /* + * Read the common CIS tuples. + */ + err = sdio_read_common_cis(card); + if (err) + goto remove; + /* * Initialize (but don't add) all present functions. */ diff --git a/drivers/mmc/core/sdio_bus.c b/drivers/mmc/core/sdio_bus.c index 78e0381f55a..461fe4837a9 100644 --- a/drivers/mmc/core/sdio_bus.c +++ b/drivers/mmc/core/sdio_bus.c @@ -95,7 +95,7 @@ static void sdio_release_func(struct device *dev) { struct sdio_func *func = dev_to_sdio_func(dev); - sdio_free_cis(func); + sdio_free_func_cis(func); kfree(func); } diff --git a/drivers/mmc/core/sdio_cis.c b/drivers/mmc/core/sdio_cis.c index b6c7342572c..ec806a1229b 100644 --- a/drivers/mmc/core/sdio_cis.c +++ b/drivers/mmc/core/sdio_cis.c @@ -16,60 +16,152 @@ #include #include +#include #include #include #include "sdio_cis.h" #include "sdio_ops.h" -static int cistpl_manfid(struct sdio_func *func, - const unsigned char *buf, - unsigned size) +static int cistpl_manfid(struct mmc_card *card, struct sdio_func *func, + const unsigned char *buf, unsigned size) { + unsigned int vendor, device; + /* TPLMID_MANF */ - func->vendor = buf[0] | (buf[1] << 8); + vendor = buf[0] | (buf[1] << 8); /* TPLMID_CARD */ - func->device = buf[2] | (buf[3] << 8); + device = buf[2] | (buf[3] << 8); + + if (func) { + func->vendor = vendor; + func->device = device; + } else { + card->cis.vendor = vendor; + card->cis.device = device; + } + + return 0; +} + +static const unsigned char speed_val[16] = + { 0, 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80 }; +static const unsigned int speed_unit[8] = + { 10000, 100000, 1000000, 10000000, 0, 0, 0, 0 }; + +static int cistpl_funce_common(struct mmc_card *card, + const unsigned char *buf, unsigned size) +{ + if (size < 0x04 || buf[0] != 0) + return -EINVAL; + + /* TPLFE_FN0_BLK_SIZE */ + card->cis.blksize = buf[1] | (buf[2] << 8); + + /* TPLFE_MAX_TRAN_SPEED */ + card->cis.max_dtr = speed_val[(buf[3] >> 3) & 15] * + speed_unit[buf[3] & 7]; + + return 0; +} + +static int cistpl_funce_func(struct sdio_func *func, + const unsigned char *buf, unsigned size) +{ + unsigned vsn; + unsigned min_size; + + vsn = func->card->cccr.sdio_vsn; + min_size = (vsn == SDIO_SDIO_REV_1_00) ? 28 : 42; + + if (size < min_size || buf[0] != 1) + return -EINVAL; + + /* TPLFE_MAX_BLK_SIZE */ + func->blksize = buf[12] | (buf[13] << 8); return 0; } +static int cistpl_funce(struct mmc_card *card, struct sdio_func *func, + const unsigned char *buf, unsigned size) +{ + int ret; + + /* + * There should be two versions of the CISTPL_FUNCE tuple, + * one for the common CIS (function 0) and a version used by + * the individual function's CIS (1-7). Yet, the later has a + * different length depending on the SDIO spec version. + */ + if (func) + ret = cistpl_funce_func(func, buf, size); + else + ret = cistpl_funce_common(card, buf, size); + + if (ret) { + printk(KERN_ERR "%s: bad CISTPL_FUNCE size %u " + "type %u\n", mmc_hostname(card->host), size, buf[0]); + return ret; + } + + return 0; +} + +typedef int (tpl_parse_t)(struct mmc_card *, struct sdio_func *, + const unsigned char *, unsigned); + struct cis_tpl { unsigned char code; unsigned char min_size; - int (*parse)(struct sdio_func *, const unsigned char *buf, unsigned size); + tpl_parse_t *parse; }; static const struct cis_tpl cis_tpl_list[] = { { 0x15, 3, /* cistpl_vers_1 */ }, { 0x20, 4, cistpl_manfid }, { 0x21, 2, /* cistpl_funcid */ }, - { 0x22, 0, /* cistpl_funce */ }, + { 0x22, 0, cistpl_funce }, }; -int sdio_read_cis(struct sdio_func *func) +static int sdio_read_cis(struct mmc_card *card, struct sdio_func *func) { int ret; struct sdio_func_tuple *this, **prev; unsigned i, ptr = 0; + /* + * Note that this works for the common CIS (function number 0) as + * well as a function's CIS * since SDIO_CCCR_CIS and SDIO_FBR_CIS + * have the same offset. + */ for (i = 0; i < 3; i++) { - unsigned char x; - ret = mmc_io_rw_direct(func->card, 0, 0, - func->num * 0x100 + SDIO_FBR_CIS + i, 0, &x); + unsigned char x, fn; + + if (func) + fn = func->num; + else + fn = 0; + + ret = mmc_io_rw_direct(card, 0, 0, + fn * 0x100 + SDIO_FBR_CIS + i, 0, &x); if (ret) return ret; ptr |= x << (i * 8); } - /* find the list tail */ - for (prev = &func->tuples; *prev; prev = &(*prev)->next); + if (func) + prev = &func->tuples; + else + prev = &card->tuples; + + BUG_ON(*prev); do { unsigned char tpl_code, tpl_link; - ret = mmc_io_rw_direct(func->card, 0, 0, ptr++, 0, &tpl_code); + ret = mmc_io_rw_direct(card, 0, 0, ptr++, 0, &tpl_code); if (ret) break; @@ -77,7 +169,7 @@ int sdio_read_cis(struct sdio_func *func) if (tpl_code == 0xff) break; - ret = mmc_io_rw_direct(func->card, 0, 0, ptr++, 0, &tpl_link); + ret = mmc_io_rw_direct(card, 0, 0, ptr++, 0, &tpl_link); if (ret) break; @@ -86,7 +178,7 @@ int sdio_read_cis(struct sdio_func *func) return -ENOMEM; for (i = 0; i < tpl_link; i++) { - ret = mmc_io_rw_direct(func->card, 0, 0, + ret = mmc_io_rw_direct(card, 0, 0, ptr + i, 0, &this->data[i]); if (ret) break; @@ -108,30 +200,45 @@ int sdio_read_cis(struct sdio_func *func) prev = &this->next; printk(KERN_DEBUG "%s: queuing CIS tuple 0x%02x length %u\n", - sdio_func_id(func), tpl_code, tpl_link); + mmc_hostname(card->host), tpl_code, tpl_link); } else { const struct cis_tpl *tpl = cis_tpl_list + i; if (tpl_link < tpl->min_size) { printk(KERN_ERR "%s: bad CIS tuple 0x%02x (length = %u, expected >= %u)\n", - sdio_func_id(func), tpl_code, tpl_link, tpl->min_size); + mmc_hostname(card->host), + tpl_code, tpl_link, tpl->min_size); ret = -EINVAL; - } else if (tpl->parse) - ret = tpl->parse(func, this->data, tpl_link); + } else if (tpl->parse) { + ret = tpl->parse(card, func, + this->data, tpl_link); + } kfree(this); } ptr += tpl_link; } while (!ret); + /* + * Link in all unknown tuples found in the common CIS so that + * drivers don't have to go digging in two places. + */ + if (func) + *prev = card->tuples; + return ret; } -void sdio_free_cis(struct sdio_func *func) +int sdio_read_common_cis(struct mmc_card *card) +{ + return sdio_read_cis(card, NULL); +} + +void sdio_free_common_cis(struct mmc_card *card) { struct sdio_func_tuple *tuple, *victim; - tuple = func->tuples; + tuple = card->tuples; while (tuple) { victim = tuple; @@ -139,6 +246,53 @@ void sdio_free_cis(struct sdio_func *func) kfree(victim); } + card->tuples = NULL; +} + +int sdio_read_func_cis(struct sdio_func *func) +{ + int ret; + + ret = sdio_read_cis(func->card, func); + if (ret) + return ret; + + /* + * Since we've linked to tuples in the card structure, + * we must make sure we have a reference to it. + */ + get_device(&func->card->dev); + + /* + * Vendor/device id is optional for function CIS, so + * copy it from the card structure as needed. + */ + if (func->vendor == 0) { + func->vendor = func->card->cis.vendor; + func->device = func->card->cis.device; + } + + return 0; +} + +void sdio_free_func_cis(struct sdio_func *func) +{ + struct sdio_func_tuple *tuple, *victim; + + tuple = func->tuples; + + while (tuple && tuple != func->card->tuples) { + victim = tuple; + tuple = tuple->next; + kfree(victim); + } + func->tuples = NULL; + + /* + * We have now removed the link to the tuples in the + * card structure, so remove the reference. + */ + put_device(&func->card->dev); } diff --git a/drivers/mmc/core/sdio_cis.h b/drivers/mmc/core/sdio_cis.h index 863d3d51637..4d903c2e425 100644 --- a/drivers/mmc/core/sdio_cis.h +++ b/drivers/mmc/core/sdio_cis.h @@ -14,7 +14,10 @@ #ifndef _MMC_SDIO_CIS_H #define _MMC_SDIO_CIS_H -int sdio_read_cis(struct sdio_func *func); -void sdio_free_cis(struct sdio_func *func); +int sdio_read_common_cis(struct mmc_card *card); +void sdio_free_common_cis(struct mmc_card *card); + +int sdio_read_func_cis(struct sdio_func *func); +void sdio_free_func_cis(struct sdio_func *func); #endif diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 520d9d29b3b..a444431e28b 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -65,8 +65,16 @@ struct sdio_cccr { high_speed:1; }; +struct sdio_cis { + unsigned short vendor; + unsigned short device; + unsigned short blksize; + unsigned int max_dtr; +}; + struct mmc_host; struct sdio_func; +struct sdio_func_tuple; #define SDIO_MAX_FUNCS 7 @@ -98,7 +106,9 @@ struct mmc_card { unsigned int sdio_funcs; /* number of SDIO functions */ struct sdio_cccr cccr; /* common card info */ + struct sdio_cis cis; /* common tuple info */ struct sdio_func *sdio_func[SDIO_MAX_FUNCS]; /* SDIO functions (devices) */ + struct sdio_func_tuple *tuples; /* unknown common tuples */ }; #define mmc_card_mmc(c) ((c)->type == MMC_TYPE_MMC) diff --git a/include/linux/mmc/sdio_func.h b/include/linux/mmc/sdio_func.h index 269067663c8..2f2b3c85441 100644 --- a/include/linux/mmc/sdio_func.h +++ b/include/linux/mmc/sdio_func.h @@ -36,6 +36,8 @@ struct sdio_func { unsigned short vendor; /* vendor id */ unsigned short device; /* device id */ + unsigned short blksize; /* maximum block size */ + unsigned int state; /* function state */ #define SDIO_STATE_PRESENT (1<<0) /* present in sysfs */ -- cgit v1.2.3 From 26074962e8f547b96614dbe248748ba2a1996ca3 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Sat, 16 Jun 2007 02:07:53 -0400 Subject: mmc: initialize mmc subsystem with subsys_initcall() The problem is that the sdio_bus must be registered before any SDIO drivers are registered against it otherwise the kernel sulks. Because the sdio_bus registration happens through module_init (equivalent to device_initcall), then any SDIO drivers linked before the SDIO core code in the kernel will be initialized first. Upcoming SDIO function drivers are likely to be located outside the drivers/mmc directory as it is common practice to group drivers according to their function rather than the bus they use. SDIO drivers are therefore likely to appear at random location in the kernel link. To make sure the sdio_bus is always initialized before any SDIO drivers, let's move the MMC init to the subsys_initcall level. Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman --- drivers/mmc/core/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 9747455928d..b8f27e5ade9 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -771,7 +771,7 @@ static void __exit mmc_exit(void) destroy_workqueue(workqueue); } -module_init(mmc_init); +subsys_initcall(mmc_init); module_exit(mmc_exit); MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 3b38bea0d976513970f947806b08b9faca418e7a Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sat, 16 Jun 2007 15:54:55 +0200 Subject: sdio: add device id table and matching Signed-off-by: Pierre Ossman --- drivers/mmc/core/sdio_bus.c | 58 ++++++++++++++++++++++++++++++++++++----- include/linux/mmc/sdio_func.h | 30 ++++++++++++++++++++- include/linux/mod_devicetable.h | 11 ++++++++ 3 files changed, 92 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/core/sdio_bus.c b/drivers/mmc/core/sdio_bus.c index 461fe4837a9..a3a89e973d9 100644 --- a/drivers/mmc/core/sdio_bus.c +++ b/drivers/mmc/core/sdio_bus.c @@ -21,14 +21,47 @@ #include "sdio_bus.h" #define dev_to_sdio_func(d) container_of(d, struct sdio_func, dev) +#define to_sdio_driver(d) container_of(d, struct sdio_driver, drv) + +static const struct sdio_device_id *sdio_match_one(struct sdio_func *func, + const struct sdio_device_id *id) +{ + if (id->class != (__u8)SDIO_ANY_ID && id->class != func->class) + return NULL; + if (id->vendor != (__u16)SDIO_ANY_ID && id->vendor != func->vendor) + return NULL; + if (id->device != (__u16)SDIO_ANY_ID && id->device != func->device) + return NULL; + return id; +} + +static const struct sdio_device_id *sdio_match_device(struct sdio_func *func, + struct sdio_driver *sdrv) +{ + const struct sdio_device_id *ids; + + ids = sdrv->id_table; + + if (ids) { + while (ids->class || ids->vendor || ids->device) { + if (sdio_match_one(func, ids)) + return ids; + ids++; + } + } + + return NULL; +} -/* - * This currently matches any SDIO function to any driver in order - * to help initial development and testing. - */ static int sdio_bus_match(struct device *dev, struct device_driver *drv) { - return 1; + struct sdio_func *func = dev_to_sdio_func(dev); + struct sdio_driver *sdrv = to_sdio_driver(drv); + + if (sdio_match_device(func, sdrv)) + return 1; + + return 0; } static int @@ -42,11 +75,24 @@ sdio_bus_uevent(struct device *dev, char **envp, int num_envp, char *buf, static int sdio_bus_probe(struct device *dev) { - return -ENODEV; + struct sdio_driver *drv = to_sdio_driver(dev->driver); + struct sdio_func *func = dev_to_sdio_func(dev); + const struct sdio_device_id *id; + + id = sdio_match_device(func, drv); + if (!id) + return -ENODEV; + + return drv->probe(func, id); } static int sdio_bus_remove(struct device *dev) { + struct sdio_driver *drv = to_sdio_driver(dev->driver); + struct sdio_func *func = dev_to_sdio_func(dev); + + drv->remove(func); + return 0; } diff --git a/include/linux/mmc/sdio_func.h b/include/linux/mmc/sdio_func.h index 2f2b3c85441..8106d399c41 100644 --- a/include/linux/mmc/sdio_func.h +++ b/include/linux/mmc/sdio_func.h @@ -12,6 +12,9 @@ #ifndef MMC_SDIO_FUNC_H #define MMC_SDIO_FUNC_H +#include +#include + struct mmc_card; /* @@ -58,13 +61,38 @@ struct sdio_func { */ struct sdio_driver { char *name; + const struct sdio_device_id *id_table; - int (*probe)(struct sdio_func *); + int (*probe)(struct sdio_func *, const struct sdio_device_id *); void (*remove)(struct sdio_func *); struct device_driver drv; }; +/** + * SDIO_DEVICE - macro used to describe a specific SDIO device + * @vend: the 16 bit manufacturer code + * @dev: the 16 bit function id + * + * This macro is used to create a struct sdio_device_id that matches a + * specific device. The class field will be set to SDIO_ANY_ID. + */ +#define SDIO_DEVICE(vend,dev) \ + .class = SDIO_ANY_ID, \ + .vendor = (vend), .device = (dev) + +/** + * SDIO_DEVICE_CLASS - macro used to describe a specific SDIO device class + * @dev_class: the 8 bit standard interface code + * + * This macro is used to create a struct sdio_device_id that matches a + * specific standard SDIO function type. The vendor and device fields will + * be set to SDIO_ANY_ID. + */ +#define SDIO_DEVICE_CLASS(dev_class) \ + .class = (dev_class), \ + .vendor = SDIO_ANY_ID, .device = SDIO_ANY_ID + extern int sdio_register_driver(struct sdio_driver *); extern void sdio_unregister_driver(struct sdio_driver *); diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index 4dc5fa8be78..e47e5951058 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -340,4 +340,15 @@ struct parisc_device_id { #define PA_HVERSION_ANY_ID 0xffff #define PA_SVERSION_ANY_ID 0xffffffff +/* SDIO */ + +#define SDIO_ANY_ID (~0) + +struct sdio_device_id { + __u8 class; /* Standard interface or SDIO_ANY_ID */ + __u16 vendor; /* Vendor or SDIO_ANY_ID */ + __u16 device; /* Device ID or SDIO_ANY_ID */ + kernel_ulong_t driver_data; /* Data private to the driver */ +}; + #endif /* LINUX_MOD_DEVICETABLE_H */ -- cgit v1.2.3 From 9eb3a94d022e6c233c0b22ec54516d35d2e87eb9 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sun, 17 Jun 2007 11:18:46 +0200 Subject: mmc: whip bus uevent handler into shape Make the mmc bus uevent callback look like all other subsystems. Signed-off-by: Pierre Ossman --- drivers/mmc/core/bus.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index 9be11ec05d8..1cc11714916 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -62,31 +62,34 @@ mmc_bus_uevent(struct device *dev, char **envp, int num_envp, char *buf, int buf_size) { struct mmc_card *card = dev_to_mmc_card(dev); - int retval = 0, i = 0, length = 0; - -#define add_env(fmt,val) do { \ - retval = add_uevent_var(envp, num_envp, &i, \ - buf, buf_size, &length, \ - fmt, val); \ - if (retval) \ - return retval; \ -} while (0); + const char *type; + int i = 0, length = 0; switch (card->type) { case MMC_TYPE_MMC: - add_env("MMC_TYPE=%s", "MMC"); + type = "MMC"; break; case MMC_TYPE_SD: - add_env("MMC_TYPE=%s", "SD"); + type = "SD"; break; case MMC_TYPE_SDIO: - add_env("MMC_TYPE=%s", "SDIO"); + type = "SDIO"; break; + default: + type = NULL; } - add_env("MMC_NAME=%s", mmc_card_name(card)); + if (type) { + if (add_uevent_var(envp, num_envp, &i, + buf, buf_size, &length, + "MMC_TYPE=%s", type)) + return -ENOMEM; + } -#undef add_env + if (add_uevent_var(envp, num_envp, &i, + buf, buf_size, &length, + "MMC_NAME=%s", mmc_card_name(card))) + return -ENOMEM; envp[i] = NULL; -- cgit v1.2.3 From d59b66c7a575cfa8e01f483875d131e42b539bbc Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sun, 17 Jun 2007 11:34:23 +0200 Subject: sdio: add modalias support Signed-off-by: Pierre Ossman --- drivers/mmc/core/sdio_bus.c | 21 ++++++++++++++++++++- scripts/mod/file2alias.c | 20 ++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/mmc/core/sdio_bus.c b/drivers/mmc/core/sdio_bus.c index a3a89e973d9..c834f515088 100644 --- a/drivers/mmc/core/sdio_bus.c +++ b/drivers/mmc/core/sdio_bus.c @@ -68,7 +68,26 @@ static int sdio_bus_uevent(struct device *dev, char **envp, int num_envp, char *buf, int buf_size) { - envp[0] = NULL; + struct sdio_func *func = dev_to_sdio_func(dev); + int i = 0, length = 0; + + if (add_uevent_var(envp, num_envp, &i, + buf, buf_size, &length, + "SDIO_CLASS=%02X", func->class)) + return -ENOMEM; + + if (add_uevent_var(envp, num_envp, &i, + buf, buf_size, &length, + "SDIO_ID=%04X:%04X", func->vendor, func->device)) + return -ENOMEM; + + if (add_uevent_var(envp, num_envp, &i, + buf, buf_size, &length, + "MODALIAS=sdio:c%02Xv%04Xd%04X", + func->class, func->vendor, func->device)) + return -ENOMEM; + + envp[i] = NULL; return 0; } diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c index 8a09021d8c5..1e5d4d69319 100644 --- a/scripts/mod/file2alias.c +++ b/scripts/mod/file2alias.c @@ -484,6 +484,22 @@ static int do_parisc_entry(const char *filename, struct parisc_device_id *id, return 1; } +/* Looks like: sdio:cNvNdN. */ +static int do_sdio_entry(const char *filename, + struct sdio_device_id *id, char *alias) +{ + id->class = TO_NATIVE(id->class); + id->vendor = TO_NATIVE(id->vendor); + id->device = TO_NATIVE(id->device); + + strcpy(alias, "sdio:"); + ADD(alias, "c", id->class != (__u8)SDIO_ANY_ID, id->class); + ADD(alias, "v", id->vendor != (__u16)SDIO_ANY_ID, id->vendor); + ADD(alias, "d", id->device != (__u16)SDIO_ANY_ID, id->device); + + return 1; +} + /* Ignore any prefix, eg. v850 prepends _ */ static inline int sym_is(const char *symbol, const char *name) { @@ -599,6 +615,10 @@ void handle_moddevtable(struct module *mod, struct elf_info *info, do_table(symval, sym->st_size, sizeof(struct parisc_device_id), "parisc", do_parisc_entry, mod); + else if (sym_is(symname, "__mod_sdio_device_table")) + do_table(symval, sym->st_size, + sizeof(struct sdio_device_id), "sdio", + do_sdio_entry, mod); } /* Now add out buffered information to the generated C source */ -- cgit v1.2.3 From bcfe66e21ef78a078bb0de0bab532701996695d3 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sun, 17 Jun 2007 11:42:21 +0200 Subject: sdio: add basic sysfs attributes Signed-off-by: Pierre Ossman --- drivers/mmc/core/sdio_bus.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/core/sdio_bus.c b/drivers/mmc/core/sdio_bus.c index c834f515088..129f0719c39 100644 --- a/drivers/mmc/core/sdio_bus.c +++ b/drivers/mmc/core/sdio_bus.c @@ -23,6 +23,37 @@ #define dev_to_sdio_func(d) container_of(d, struct sdio_func, dev) #define to_sdio_driver(d) container_of(d, struct sdio_driver, drv) +/* show configuration fields */ +#define sdio_config_attr(field, format_string) \ +static ssize_t \ +field##_show(struct device *dev, struct device_attribute *attr, char *buf) \ +{ \ + struct sdio_func *func; \ + \ + func = dev_to_sdio_func (dev); \ + return sprintf (buf, format_string, func->field); \ +} + +sdio_config_attr(class, "0x%02x\n"); +sdio_config_attr(vendor, "0x%04x\n"); +sdio_config_attr(device, "0x%04x\n"); + +static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sdio_func *func = dev_to_sdio_func (dev); + + return sprintf(buf, "sdio:c%02Xv%04Xd%04X\n", + func->class, func->vendor, func->device); +} + +struct device_attribute sdio_dev_attrs[] = { + __ATTR_RO(class), + __ATTR_RO(vendor), + __ATTR_RO(device), + __ATTR_RO(modalias), + __ATTR_NULL, +}; + static const struct sdio_device_id *sdio_match_one(struct sdio_func *func, const struct sdio_device_id *id) { @@ -117,6 +148,7 @@ static int sdio_bus_remove(struct device *dev) static struct bus_type sdio_bus_type = { .name = "sdio", + .dev_attrs = sdio_dev_attrs, .match = sdio_bus_match, .uevent = sdio_bus_uevent, .probe = sdio_bus_probe, -- cgit v1.2.3 From 2342f3323c9a76367a1d7f9a35525ee3cb3911df Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Sat, 30 Jun 2007 16:21:52 +0200 Subject: sdio: allow for mmc_claim_host to be aborted It is sometimes necessary to give up on trying to claim the host lock, especially if that happens in a thread that has to be stopped. While at it, fix the description for mmc_claim_host() which was wrong. Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman --- drivers/mmc/core/core.c | 22 ++++++++++++++++------ include/linux/mmc/core.h | 13 ++++++++++++- 2 files changed, 28 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index b8f27e5ade9..d1e4b0849e3 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -273,15 +273,20 @@ void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card) EXPORT_SYMBOL(mmc_set_data_timeout); /** - * mmc_claim_host - exclusively claim a host + * __mmc_claim_host - exclusively claim a host * @host: mmc host to claim + * @abort: whether or not the operation should be aborted * - * Claim a host for a set of operations. + * Claim a host for a set of operations. If @abort is non null and + * dereference a non-zero value then this will return prematurely with + * that non-zero value without acquiring the lock. Returns zero + * with the lock held otherwise. */ -void mmc_claim_host(struct mmc_host *host) +int __mmc_claim_host(struct mmc_host *host, atomic_t *abort) { DECLARE_WAITQUEUE(wait, current); unsigned long flags; + int stop; might_sleep(); @@ -289,19 +294,24 @@ void mmc_claim_host(struct mmc_host *host) spin_lock_irqsave(&host->lock, flags); while (1) { set_current_state(TASK_UNINTERRUPTIBLE); - if (!host->claimed) + stop = abort ? atomic_read(abort) : 0; + if (stop || !host->claimed) break; spin_unlock_irqrestore(&host->lock, flags); schedule(); spin_lock_irqsave(&host->lock, flags); } set_current_state(TASK_RUNNING); - host->claimed = 1; + if (!stop) + host->claimed = 1; + else + wake_up(&host->wq); spin_unlock_irqrestore(&host->lock, flags); remove_wait_queue(&host->wq, &wait); + return stop; } -EXPORT_SYMBOL(mmc_claim_host); +EXPORT_SYMBOL(__mmc_claim_host); /** * mmc_release_host - release a host diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 43a92736be6..8945da9b54f 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -114,7 +114,18 @@ extern int mmc_wait_for_app_cmd(struct mmc_host *, struct mmc_card *, extern void mmc_set_data_timeout(struct mmc_data *, const struct mmc_card *); -extern void mmc_claim_host(struct mmc_host *host); +extern int __mmc_claim_host(struct mmc_host *host, atomic_t *abort); extern void mmc_release_host(struct mmc_host *host); +/** + * mmc_claim_host - exclusively claim a host + * @host: mmc host to claim + * + * Claim a host for a set of operations. + */ +static inline void mmc_claim_host(struct mmc_host *host) +{ + __mmc_claim_host(host, NULL); +} + #endif -- cgit v1.2.3 From d1496c39e500857b8949cdb91af24e0eb8aae4d0 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Sat, 30 Jun 2007 16:29:41 +0200 Subject: sdio: core support for SDIO function interrupt Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman --- drivers/mmc/core/Makefile | 2 +- drivers/mmc/core/sdio_bus.c | 8 ++ drivers/mmc/core/sdio_irq.c | 237 ++++++++++++++++++++++++++++++++++++++++++ include/linux/mmc/host.h | 4 + include/linux/mmc/sdio_func.h | 7 ++ 5 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 drivers/mmc/core/sdio_irq.c (limited to 'drivers') diff --git a/drivers/mmc/core/Makefile b/drivers/mmc/core/Makefile index 05d69fc72c1..4985807257a 100644 --- a/drivers/mmc/core/Makefile +++ b/drivers/mmc/core/Makefile @@ -10,5 +10,5 @@ obj-$(CONFIG_MMC) += mmc_core.o mmc_core-y := core.o sysfs.o bus.o host.o \ mmc.o mmc_ops.o sd.o sd_ops.o \ sdio.o sdio_ops.o sdio_bus.o \ - sdio_cis.o sdio_io.o + sdio_cis.o sdio_io.o sdio_irq.o diff --git a/drivers/mmc/core/sdio_bus.c b/drivers/mmc/core/sdio_bus.c index 129f0719c39..24072445491 100644 --- a/drivers/mmc/core/sdio_bus.c +++ b/drivers/mmc/core/sdio_bus.c @@ -143,6 +143,14 @@ static int sdio_bus_remove(struct device *dev) drv->remove(func); + if (func->irq_handler) { + printk(KERN_WARNING "WARNING: driver %s did not remove " + "its interrupt handler!\n", drv->name); + sdio_claim_host(func); + sdio_release_irq(func); + sdio_release_host(func); + } + return 0; } diff --git a/drivers/mmc/core/sdio_irq.c b/drivers/mmc/core/sdio_irq.c new file mode 100644 index 00000000000..01922d29241 --- /dev/null +++ b/drivers/mmc/core/sdio_irq.c @@ -0,0 +1,237 @@ +/* + * linux/drivers/mmc/core/sdio_irq.c + * + * Author: Nicolas Pitre + * Created: June 18, 2007 + * Copyright: MontaVista Software Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "sdio_ops.h" + +static int process_sdio_pending_irqs(struct mmc_card *card) +{ + int i, ret; + unsigned char pending; + + ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_INTx, 0, &pending); + if (ret) { + printk(KERN_DEBUG "%s: error %d reading SDIO_CCCR_INTx\n", + mmc_card_id(card), ret); + return ret; + } + + for (i = 1; i <= 7; i++) { + if (pending & (1 << i)) { + struct sdio_func *func = card->sdio_func[i - 1]; + if (!func) { + printk(KERN_WARNING "%s: pending IRQ for " + "non-existant function\n", + sdio_func_id(func)); + } else if (func->irq_handler) { + func->irq_handler(func); + } else + printk(KERN_WARNING "%s: pending IRQ with no handler\n", + sdio_func_id(func)); + } + } + + return 0; +} + +static int sdio_irq_thread(void *_host) +{ + struct mmc_host *host = _host; + struct sched_param param = { .sched_priority = 1 }; + unsigned long period; + int ret; + + sched_setscheduler(current, SCHED_FIFO, ¶m); + + /* + * We want to allow for SDIO cards to work even on non SDIO + * aware hosts. One thing that non SDIO host cannot do is + * asynchronous notification of pending SDIO card interrupts + * hence we poll for them in that case. + */ + period = msecs_to_jiffies(10); + + pr_debug("%s: IRQ thread started (poll period = %lu jiffies)\n", + mmc_hostname(host), period); + + do { + /* + * We claim the host here on drivers behalf for a couple + * reasons: + * + * 1) it is already needed to retrieve the CCCR_INTx; + * 2) we want the driver(s) to clear the IRQ condition ASAP; + * 3) we need to control the abort condition locally. + * + * Just like traditional hard IRQ handlers, we expect SDIO + * IRQ handlers to be quick and to the point, so that the + * holding of the host lock does not cover too much work + * that doesn't require that lock to be held. + */ + ret = __mmc_claim_host(host, &host->sdio_irq_thread_abort); + if (ret) + break; + ret = process_sdio_pending_irqs(host->card); + mmc_release_host(host); + + /* + * Give other threads a chance to run in the presence of + * errors. FIXME: determine if due to card removal and + * possibly exit this thread if so. + */ + if (ret) + ssleep(1); + + set_task_state(current, TASK_INTERRUPTIBLE); + if (!kthread_should_stop()) + schedule_timeout(period); + set_task_state(current, TASK_RUNNING); + } while (!kthread_should_stop()); + + pr_debug("%s: IRQ thread exiting with code %d\n", + mmc_hostname(host), ret); + + return ret; +} + +static int sdio_card_irq_get(struct mmc_card *card) +{ + struct mmc_host *host = card->host; + + BUG_ON(!host->claimed); + + if (!host->sdio_irqs++) { + atomic_set(&host->sdio_irq_thread_abort, 0); + host->sdio_irq_thread = + kthread_run(sdio_irq_thread, host, "ksdiorqd"); + if (IS_ERR(host->sdio_irq_thread)) { + int err = PTR_ERR(host->sdio_irq_thread); + host->sdio_irqs--; + return err; + } + } + + return 0; +} + +static int sdio_card_irq_put(struct mmc_card *card) +{ + struct mmc_host *host = card->host; + + BUG_ON(!host->claimed); + BUG_ON(host->sdio_irqs < 1); + + if (!--host->sdio_irqs) { + atomic_set(&host->sdio_irq_thread_abort, 1); + kthread_stop(host->sdio_irq_thread); + } + + return 0; +} + +/** + * sdio_claim_irq - claim the IRQ for a SDIO function + * @func: SDIO function + * @handler: IRQ handler callback + * + * Claim and activate the IRQ for the given SDIO function. The provided + * handler will be called when that IRQ is asserted. The host is always + * claimed already when the handler is called so the handler must not + * call sdio_claim_host() nor sdio_release_host(). + */ +int sdio_claim_irq(struct sdio_func *func, sdio_irq_handler_t *handler) +{ + int ret; + unsigned char reg; + + BUG_ON(!func); + BUG_ON(!func->card); + + pr_debug("SDIO: Enabling IRQ for %s...\n", sdio_func_id(func)); + + if (func->irq_handler) { + pr_debug("SDIO: IRQ for %s already in use.\n", sdio_func_id(func)); + return -EBUSY; + } + + ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IENx, 0, ®); + if (ret) + return ret; + + reg |= 1 << func->num; + + reg |= 1; /* Master interrupt enable */ + + ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, reg, NULL); + if (ret) + return ret; + + func->irq_handler = handler; + ret = sdio_card_irq_get(func->card); + if (ret) + func->irq_handler = NULL; + + return ret; +} +EXPORT_SYMBOL_GPL(sdio_claim_irq); + +/** + * sdio_release_irq - release the IRQ for a SDIO function + * @func: SDIO function + * + * Disable and release the IRQ for the given SDIO function. + */ +int sdio_release_irq(struct sdio_func *func) +{ + int ret; + unsigned char reg; + + BUG_ON(!func); + BUG_ON(!func->card); + + pr_debug("SDIO: Disabling IRQ for %s...\n", sdio_func_id(func)); + + if (func->irq_handler) { + func->irq_handler = NULL; + sdio_card_irq_put(func->card); + } + + ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IENx, 0, ®); + if (ret) + return ret; + + reg &= ~(1 << func->num); + + /* Disable master interrupt with the last function interrupt */ + if (!(reg & 0xFE)) + reg = 0; + + ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, reg, NULL); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(sdio_release_irq); + diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 8e2642ebf02..00dc1809494 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -123,6 +123,10 @@ struct mmc_host { unsigned int bus_refs; /* reference counter */ unsigned int bus_dead:1; /* bus has been released */ + unsigned int sdio_irqs; + struct task_struct *sdio_irq_thread; + atomic_t sdio_irq_thread_abort; + unsigned long private[0] ____cacheline_aligned; }; diff --git a/include/linux/mmc/sdio_func.h b/include/linux/mmc/sdio_func.h index 8106d399c41..a8d268c9c27 100644 --- a/include/linux/mmc/sdio_func.h +++ b/include/linux/mmc/sdio_func.h @@ -16,6 +16,9 @@ #include struct mmc_card; +struct sdio_func; + +typedef void (sdio_irq_handler_t)(struct sdio_func *); /* * SDIO function CIS tuple (unknown to the core) @@ -33,6 +36,7 @@ struct sdio_func_tuple { struct sdio_func { struct mmc_card *card; /* the card this device belongs to */ struct device dev; /* the device */ + sdio_irq_handler_t *irq_handler; /* IRQ callback */ unsigned int num; /* function number */ unsigned char class; /* standard interface class */ @@ -105,6 +109,9 @@ extern void sdio_release_host(struct sdio_func *func); extern int sdio_enable_func(struct sdio_func *func); extern int sdio_disable_func(struct sdio_func *func); +extern int sdio_claim_irq(struct sdio_func *func, sdio_irq_handler_t *handler); +extern int sdio_release_irq(struct sdio_func *func); + extern unsigned char sdio_readb(struct sdio_func *func, unsigned int addr, int *err_ret); -- cgit v1.2.3 From 6e418a9d26ab4fd44b3e07dc1158027cbdf0a919 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Sat, 30 Jun 2007 02:04:21 -0400 Subject: sdio: UART/GPS driver This currently only accepts the GPS class since that's all I have for testing. Tested with a Matsushita GPS and gpsd version 2.34. Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman --- drivers/mmc/card/Kconfig | 7 + drivers/mmc/card/Makefile | 2 + drivers/mmc/card/sdio_uart.c | 1077 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1086 insertions(+) create mode 100644 drivers/mmc/card/sdio_uart.c (limited to 'drivers') diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig index a49cb9737cd..aa8a4e46194 100644 --- a/drivers/mmc/card/Kconfig +++ b/drivers/mmc/card/Kconfig @@ -32,3 +32,10 @@ config MMC_BLOCK_BOUNCE If unsure, say Y here. +config SDIO_UART + tristate "SDIO UART/GPS class support" + depends on MMC + help + SDIO function driver for SDIO cards that implements the UART + class, as well as the GPS class which appears like a UART. + diff --git a/drivers/mmc/card/Makefile b/drivers/mmc/card/Makefile index cf8c939867f..fc5a784cfa1 100644 --- a/drivers/mmc/card/Makefile +++ b/drivers/mmc/card/Makefile @@ -9,3 +9,5 @@ endif obj-$(CONFIG_MMC_BLOCK) += mmc_block.o mmc_block-objs := block.o queue.o +obj-$(CONFIG_SDIO_UART) += sdio_uart.o + diff --git a/drivers/mmc/card/sdio_uart.c b/drivers/mmc/card/sdio_uart.c new file mode 100644 index 00000000000..e4d9e85ff58 --- /dev/null +++ b/drivers/mmc/card/sdio_uart.c @@ -0,0 +1,1077 @@ +/* + * linux/drivers/mmc/card/sdio_uart.c - SDIO UART/GPS driver + * + * Based on drivers/serial/8250.c and drivers/serial/serial_core.c + * by Russell King. + * + * Author: Nicolas Pitre + * Created: June 15, 2007 + * Copyright: MontaVista Software, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +/* + * Note: Although this driver assumes a 16550A-like UART implementation, + * it is not possible to leverage the common 8250/16550 driver, nor the + * core UART infrastructure, as they assumes direct access to the hardware + * registers, often under a spinlock. This is not possible in the SDIO + * context as SDIO access functions must be able to sleep. + * + * Because we need to lock the SDIO host to ensure an exclusive access to + * the card, we simply rely on that lock to also prevent and serialize + * concurrent access to the same port. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +#define UART_NR 8 /* Number of UARTs this driver can handle */ + + +#define UART_XMIT_SIZE PAGE_SIZE +#define WAKEUP_CHARS 256 + +#define circ_empty(circ) ((circ)->head == (circ)->tail) +#define circ_clear(circ) ((circ)->head = (circ)->tail = 0) + +#define circ_chars_pending(circ) \ + (CIRC_CNT((circ)->head, (circ)->tail, UART_XMIT_SIZE)) + +#define circ_chars_free(circ) \ + (CIRC_SPACE((circ)->head, (circ)->tail, UART_XMIT_SIZE)) + + +struct uart_icount { + __u32 cts; + __u32 dsr; + __u32 rng; + __u32 dcd; + __u32 rx; + __u32 tx; + __u32 frame; + __u32 overrun; + __u32 parity; + __u32 brk; +}; + +struct sdio_uart_port { + struct kref kref; + struct tty_struct *tty; + unsigned int index; + unsigned int opened; + struct mutex open_lock; + struct sdio_func *func; + struct mutex func_lock; + unsigned int regs_offset; + struct circ_buf xmit; + spinlock_t write_lock; + struct uart_icount icount; + unsigned int uartclk; + unsigned int mctrl; + unsigned int read_status_mask; + unsigned int ignore_status_mask; + unsigned char x_char; + unsigned char ier; + unsigned char lcr; +}; + +static struct sdio_uart_port *sdio_uart_table[UART_NR]; +static DEFINE_SPINLOCK(sdio_uart_table_lock); + +static int sdio_uart_add_port(struct sdio_uart_port *port) +{ + int index, ret = -EBUSY; + + kref_init(&port->kref); + mutex_init(&port->open_lock); + mutex_init(&port->func_lock); + spin_lock_init(&port->write_lock); + + spin_lock(&sdio_uart_table_lock); + for (index = 0; index < UART_NR; index++) { + if (!sdio_uart_table[index]) { + port->index = index; + sdio_uart_table[index] = port; + ret = 0; + break; + } + } + spin_unlock(&sdio_uart_table_lock); + + return ret; +} + +static struct sdio_uart_port *sdio_uart_port_get(unsigned index) +{ + struct sdio_uart_port *port; + + if (index >= UART_NR) + return NULL; + + spin_lock(&sdio_uart_table_lock); + port = sdio_uart_table[index]; + if (port) + kref_get(&port->kref); + spin_unlock(&sdio_uart_table_lock); + + return port; +} + +static void sdio_uart_port_destroy(struct kref *kref) +{ + struct sdio_uart_port *port = + container_of(kref, struct sdio_uart_port, kref); + kfree(port); +} + +static void sdio_uart_port_put(struct sdio_uart_port *port) +{ + kref_put(&port->kref, sdio_uart_port_destroy); +} + +static void sdio_uart_port_remove(struct sdio_uart_port *port) +{ + struct sdio_func *func; + + BUG_ON(sdio_uart_table[port->index] != port); + + spin_lock(&sdio_uart_table_lock); + sdio_uart_table[port->index] = NULL; + spin_unlock(&sdio_uart_table_lock); + + /* + * We're killing a port that potentially still is in use by + * the tty layer. Be careful to prevent any further access + * to the SDIO function and arrange for the tty layer to + * give up on that port ASAP. + * Beware: the lock ordering is critical. + */ + mutex_lock(&port->open_lock); + mutex_lock(&port->func_lock); + func = port->func; + sdio_claim_host(func); + port->func = NULL; + mutex_unlock(&port->func_lock); + if (port->opened) + tty_hangup(port->tty); + mutex_unlock(&port->open_lock); + sdio_release_irq(func); + sdio_disable_func(func); + sdio_release_host(func); + + sdio_uart_port_put(port); +} + +static int sdio_uart_claim_func(struct sdio_uart_port *port) +{ + mutex_lock(&port->func_lock); + if (unlikely(!port->func)) { + mutex_unlock(&port->func_lock); + return -ENODEV; + } + sdio_claim_host(port->func); + mutex_unlock(&port->func_lock); + return 0; +} + +static inline void sdio_uart_release_func(struct sdio_uart_port *port) +{ + sdio_release_host(port->func); +} + +static inline unsigned int sdio_in(struct sdio_uart_port *port, int offset) +{ + unsigned char c; + c = sdio_readb(port->func, port->regs_offset + offset, NULL); + return c; +} + +static inline void sdio_out(struct sdio_uart_port *port, int offset, int value) +{ + sdio_writeb(port->func, value, port->regs_offset + offset, NULL); +} + +static unsigned int sdio_uart_get_mctrl(struct sdio_uart_port *port) +{ + unsigned char status; + unsigned int ret; + + status = sdio_in(port, UART_MSR); + + ret = 0; + if (status & UART_MSR_DCD) + ret |= TIOCM_CAR; + if (status & UART_MSR_RI) + ret |= TIOCM_RNG; + if (status & UART_MSR_DSR) + ret |= TIOCM_DSR; + if (status & UART_MSR_CTS) + ret |= TIOCM_CTS; + return ret; +} + +static void sdio_uart_write_mctrl(struct sdio_uart_port *port, unsigned int mctrl) +{ + unsigned char mcr = 0; + + if (mctrl & TIOCM_RTS) + mcr |= UART_MCR_RTS; + if (mctrl & TIOCM_DTR) + mcr |= UART_MCR_DTR; + if (mctrl & TIOCM_OUT1) + mcr |= UART_MCR_OUT1; + if (mctrl & TIOCM_OUT2) + mcr |= UART_MCR_OUT2; + if (mctrl & TIOCM_LOOP) + mcr |= UART_MCR_LOOP; + + sdio_out(port, UART_MCR, mcr); +} + +static inline void sdio_uart_update_mctrl(struct sdio_uart_port *port, + unsigned int set, unsigned int clear) +{ + unsigned int old; + + old = port->mctrl; + port->mctrl = (old & ~clear) | set; + if (old != port->mctrl) + sdio_uart_write_mctrl(port, port->mctrl); +} + +#define sdio_uart_set_mctrl(port, x) sdio_uart_update_mctrl(port, x, 0) +#define sdio_uart_clear_mctrl(port, x) sdio_uart_update_mctrl(port, 0, x) + +static void sdio_uart_change_speed(struct sdio_uart_port *port, + struct ktermios *termios, + struct ktermios *old) +{ + unsigned char cval, fcr = 0; + unsigned int baud, quot; + + switch (termios->c_cflag & CSIZE) { + case CS5: + cval = UART_LCR_WLEN5; + break; + case CS6: + cval = UART_LCR_WLEN6; + break; + case CS7: + cval = UART_LCR_WLEN7; + break; + default: + case CS8: + cval = UART_LCR_WLEN8; + break; + } + + if (termios->c_cflag & CSTOPB) + cval |= UART_LCR_STOP; + if (termios->c_cflag & PARENB) + cval |= UART_LCR_PARITY; + if (!(termios->c_cflag & PARODD)) + cval |= UART_LCR_EPAR; + + for (;;) { + baud = tty_termios_baud_rate(termios); + if (baud == 0) + baud = 9600; /* Special case: B0 rate. */ + if (baud <= port->uartclk) + break; + /* + * Oops, the quotient was zero. Try again with the old + * baud rate if possible, otherwise default to 9600. + */ + termios->c_cflag &= ~CBAUD; + if (old) { + termios->c_cflag |= old->c_cflag & CBAUD; + old = NULL; + } else + termios->c_cflag |= B9600; + } + quot = (2 * port->uartclk + baud) / (2 * baud); + + if (baud < 2400) + fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_1; + else + fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_10; + + port->read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR; + if (termios->c_iflag & INPCK) + port->read_status_mask |= UART_LSR_FE | UART_LSR_PE; + if (termios->c_iflag & (BRKINT | PARMRK)) + port->read_status_mask |= UART_LSR_BI; + + /* + * Characters to ignore + */ + port->ignore_status_mask = 0; + if (termios->c_iflag & IGNPAR) + port->ignore_status_mask |= UART_LSR_PE | UART_LSR_FE; + if (termios->c_iflag & IGNBRK) { + port->ignore_status_mask |= UART_LSR_BI; + /* + * If we're ignoring parity and break indicators, + * ignore overruns too (for real raw support). + */ + if (termios->c_iflag & IGNPAR) + port->ignore_status_mask |= UART_LSR_OE; + } + + /* + * ignore all characters if CREAD is not set + */ + if ((termios->c_cflag & CREAD) == 0) + port->ignore_status_mask |= UART_LSR_DR; + + /* + * CTS flow control flag and modem status interrupts + */ + port->ier &= ~UART_IER_MSI; + if ((termios->c_cflag & CRTSCTS) || !(termios->c_cflag & CLOCAL)) + port->ier |= UART_IER_MSI; + + port->lcr = cval; + + sdio_out(port, UART_IER, port->ier); + sdio_out(port, UART_LCR, cval | UART_LCR_DLAB); + sdio_out(port, UART_DLL, quot & 0xff); + sdio_out(port, UART_DLM, quot >> 8); + sdio_out(port, UART_LCR, cval); + sdio_out(port, UART_FCR, fcr); + + sdio_uart_write_mctrl(port, port->mctrl); +} + +static void sdio_uart_start_tx(struct sdio_uart_port *port) +{ + if (!(port->ier & UART_IER_THRI)) { + port->ier |= UART_IER_THRI; + sdio_out(port, UART_IER, port->ier); + } +} + +static void sdio_uart_stop_tx(struct sdio_uart_port *port) +{ + if (port->ier & UART_IER_THRI) { + port->ier &= ~UART_IER_THRI; + sdio_out(port, UART_IER, port->ier); + } +} + +static void sdio_uart_stop_rx(struct sdio_uart_port *port) +{ + port->ier &= ~UART_IER_RLSI; + port->read_status_mask &= ~UART_LSR_DR; + sdio_out(port, UART_IER, port->ier); +} + +static void sdio_uart_receive_chars(struct sdio_uart_port *port, int *status) +{ + struct tty_struct *tty = port->tty; + unsigned int ch, flag; + int max_count = 256; + + do { + ch = sdio_in(port, UART_RX); + flag = TTY_NORMAL; + port->icount.rx++; + + if (unlikely(*status & (UART_LSR_BI | UART_LSR_PE | + UART_LSR_FE | UART_LSR_OE))) { + /* + * For statistics only + */ + if (*status & UART_LSR_BI) { + *status &= ~(UART_LSR_FE | UART_LSR_PE); + port->icount.brk++; + } else if (*status & UART_LSR_PE) + port->icount.parity++; + else if (*status & UART_LSR_FE) + port->icount.frame++; + if (*status & UART_LSR_OE) + port->icount.overrun++; + + /* + * Mask off conditions which should be ignored. + */ + *status &= port->read_status_mask; + if (*status & UART_LSR_BI) { + flag = TTY_BREAK; + } else if (*status & UART_LSR_PE) + flag = TTY_PARITY; + else if (*status & UART_LSR_FE) + flag = TTY_FRAME; + } + + if ((*status & port->ignore_status_mask & ~UART_LSR_OE) == 0) + tty_insert_flip_char(tty, ch, flag); + + /* + * Overrun is special. Since it's reported immediately, + * it doesn't affect the current character. + */ + if (*status & ~port->ignore_status_mask & UART_LSR_OE) + tty_insert_flip_char(tty, 0, TTY_OVERRUN); + + *status = sdio_in(port, UART_LSR); + } while ((*status & UART_LSR_DR) && (max_count-- > 0)); + tty_flip_buffer_push(tty); +} + +static void sdio_uart_transmit_chars(struct sdio_uart_port *port) +{ + struct circ_buf *xmit = &port->xmit; + int count; + + if (port->x_char) { + sdio_out(port, UART_TX, port->x_char); + port->icount.tx++; + port->x_char = 0; + return; + } + if (circ_empty(xmit) || port->tty->stopped || port->tty->hw_stopped) { + sdio_uart_stop_tx(port); + return; + } + + count = 16; + do { + sdio_out(port, UART_TX, xmit->buf[xmit->tail]); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + port->icount.tx++; + if (circ_empty(xmit)) + break; + } while (--count > 0); + + if (circ_chars_pending(xmit) < WAKEUP_CHARS) + tty_wakeup(port->tty); + + if (circ_empty(xmit)) + sdio_uart_stop_tx(port); +} + +static void sdio_uart_check_modem_status(struct sdio_uart_port *port) +{ + int status; + + status = sdio_in(port, UART_MSR); + + if ((status & UART_MSR_ANY_DELTA) == 0) + return; + + if (status & UART_MSR_TERI) + port->icount.rng++; + if (status & UART_MSR_DDSR) + port->icount.dsr++; + if (status & UART_MSR_DDCD) + port->icount.dcd++; + if (status & UART_MSR_DCTS) { + port->icount.cts++; + if (port->tty->termios->c_cflag & CRTSCTS) { + int cts = (status & UART_MSR_CTS); + if (port->tty->hw_stopped) { + if (cts) { + port->tty->hw_stopped = 0; + sdio_uart_start_tx(port); + tty_wakeup(port->tty); + } + } else { + if (!cts) { + port->tty->hw_stopped = 1; + sdio_uart_stop_tx(port); + } + } + } + } +} + +/* + * This handles the interrupt from one port. + */ +static void sdio_uart_irq(struct sdio_func *func) +{ + struct sdio_uart_port *port = sdio_get_drvdata(func); + unsigned int iir, lsr; + + iir = sdio_in(port, UART_IIR); + if (iir & UART_IIR_NO_INT) + return; + lsr = sdio_in(port, UART_LSR); + if (lsr & UART_LSR_DR) + sdio_uart_receive_chars(port, &lsr); + sdio_uart_check_modem_status(port); + if (lsr & UART_LSR_THRE) + sdio_uart_transmit_chars(port); +} + +static int sdio_uart_startup(struct sdio_uart_port *port) +{ + unsigned long page; + int ret; + + /* + * Set the TTY IO error marker - we will only clear this + * once we have successfully opened the port. + */ + set_bit(TTY_IO_ERROR, &port->tty->flags); + + /* Initialise and allocate the transmit buffer. */ + page = __get_free_page(GFP_KERNEL); + if (!page) + return -ENOMEM; + port->xmit.buf = (unsigned char *)page; + circ_clear(&port->xmit); + + ret = sdio_uart_claim_func(port); + if (ret) + goto err1; + ret = sdio_enable_func(port->func); + if (ret) + goto err2; + ret = sdio_claim_irq(port->func, sdio_uart_irq); + if (ret) + goto err3; + + /* + * Clear the FIFO buffers and disable them. + * (they will be reenabled in sdio_change_speed()) + */ + sdio_out(port, UART_FCR, UART_FCR_ENABLE_FIFO); + sdio_out(port, UART_FCR, UART_FCR_ENABLE_FIFO | + UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT); + sdio_out(port, UART_FCR, 0); + + /* + * Clear the interrupt registers. + */ + (void) sdio_in(port, UART_LSR); + (void) sdio_in(port, UART_RX); + (void) sdio_in(port, UART_IIR); + (void) sdio_in(port, UART_MSR); + + /* + * Now, initialize the UART + */ + sdio_out(port, UART_LCR, UART_LCR_WLEN8); + + port->ier = UART_IER_RLSI | UART_IER_RDI | UART_IER_RTOIE | UART_IER_UUE; + port->mctrl = TIOCM_OUT2; + + sdio_uart_change_speed(port, port->tty->termios, NULL); + + if (port->tty->termios->c_cflag & CBAUD) + sdio_uart_set_mctrl(port, TIOCM_RTS | TIOCM_DTR); + + if (port->tty->termios->c_cflag & CRTSCTS) + if (!(sdio_uart_get_mctrl(port) & TIOCM_CTS)) + port->tty->hw_stopped = 1; + + clear_bit(TTY_IO_ERROR, &port->tty->flags); + + /* Kick the IRQ handler once while we're still holding the host lock */ + sdio_uart_irq(port->func); + + sdio_uart_release_func(port); + return 0; + +err3: + sdio_disable_func(port->func); +err2: + sdio_uart_release_func(port); +err1: + free_page((unsigned long)port->xmit.buf); + return ret; +} + +static void sdio_uart_shutdown(struct sdio_uart_port *port) +{ + int ret; + + ret = sdio_uart_claim_func(port); + if (ret) + goto skip; + + sdio_uart_stop_rx(port); + + /* TODO: wait here for TX FIFO to drain */ + + /* Turn off DTR and RTS early. */ + if (port->tty->termios->c_cflag & HUPCL) + sdio_uart_clear_mctrl(port, TIOCM_DTR | TIOCM_RTS); + + /* Disable interrupts from this port */ + sdio_release_irq(port->func); + port->ier = 0; + sdio_out(port, UART_IER, 0); + + sdio_uart_clear_mctrl(port, TIOCM_OUT2); + + /* Disable break condition and FIFOs. */ + port->lcr &= ~UART_LCR_SBC; + sdio_out(port, UART_LCR, port->lcr); + sdio_out(port, UART_FCR, UART_FCR_ENABLE_FIFO | + UART_FCR_CLEAR_RCVR | + UART_FCR_CLEAR_XMIT); + sdio_out(port, UART_FCR, 0); + + sdio_disable_func(port->func); + + sdio_uart_release_func(port); + +skip: + /* Free the transmit buffer page. */ + free_page((unsigned long)port->xmit.buf); +} + +static int sdio_uart_open (struct tty_struct *tty, struct file * filp) +{ + struct sdio_uart_port *port; + int ret; + + port = sdio_uart_port_get(tty->index); + if (!port) + return -ENODEV; + + mutex_lock(&port->open_lock); + + /* + * Make sure not to mess up with a dead port + * which has not been closed yet. + */ + if (tty->driver_data && tty->driver_data != port) { + mutex_unlock(&port->open_lock); + sdio_uart_port_put(port); + return -EBUSY; + } + + if (!port->opened) { + tty->driver_data = port; + port->tty = tty; + ret = sdio_uart_startup(port); + if (ret) { + tty->driver_data = NULL; + port->tty = NULL; + mutex_unlock(&port->open_lock); + sdio_uart_port_put(port); + return ret; + } + } + port->opened++; + mutex_unlock(&port->open_lock); + return 0; +} + +static void sdio_uart_close(struct tty_struct *tty, struct file * filp) +{ + struct sdio_uart_port *port = tty->driver_data; + + if (!port) + return; + + mutex_lock(&port->open_lock); + BUG_ON(!port->opened); + + /* + * This is messy. The tty layer calls us even when open() + * returned an error. Ignore this close request if tty->count + * is larger than port->count. + */ + if (tty->count > port->opened) { + mutex_unlock(&port->open_lock); + return; + } + + if (--port->opened == 0) { + tty->closing = 1; + sdio_uart_shutdown(port); + tty_ldisc_flush(tty); + port->tty = NULL; + tty->driver_data = NULL; + tty->closing = 0; + } + mutex_unlock(&port->open_lock); + sdio_uart_port_put(port); +} + +static int sdio_uart_write(struct tty_struct * tty, const unsigned char *buf, + int count) +{ + struct sdio_uart_port *port = tty->driver_data; + struct circ_buf *circ = &port->xmit; + int c, ret = 0; + + if (!port->func) + return -ENODEV; + + spin_lock(&port->write_lock); + while (1) { + c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE); + if (count < c) + c = count; + if (c <= 0) + break; + memcpy(circ->buf + circ->head, buf, c); + circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1); + buf += c; + count -= c; + ret += c; + } + spin_unlock(&port->write_lock); + + if ( !(port->ier & UART_IER_THRI)) { + int err = sdio_uart_claim_func(port); + if (!err) { + sdio_uart_start_tx(port); + sdio_uart_irq(port->func); + sdio_uart_release_func(port); + } else + ret = err; + } + + return ret; +} + +static int sdio_uart_write_room(struct tty_struct *tty) +{ + struct sdio_uart_port *port = tty->driver_data; + return port ? circ_chars_free(&port->xmit) : 0; +} + +static int sdio_uart_chars_in_buffer(struct tty_struct *tty) +{ + struct sdio_uart_port *port = tty->driver_data; + return port ? circ_chars_pending(&port->xmit) : 0; +} + +static void sdio_uart_send_xchar(struct tty_struct *tty, char ch) +{ + struct sdio_uart_port *port = tty->driver_data; + + port->x_char = ch; + if (ch && !(port->ier & UART_IER_THRI)) { + if (sdio_uart_claim_func(port) != 0) + return; + sdio_uart_start_tx(port); + sdio_uart_irq(port->func); + sdio_uart_release_func(port); + } +} + +static void sdio_uart_throttle(struct tty_struct *tty) +{ + struct sdio_uart_port *port = tty->driver_data; + + if (!I_IXOFF(tty) && !(tty->termios->c_cflag & CRTSCTS)) + return; + + if (sdio_uart_claim_func(port) != 0) + return; + + if (I_IXOFF(tty)) { + port->x_char = STOP_CHAR(tty); + sdio_uart_start_tx(port); + } + + if (tty->termios->c_cflag & CRTSCTS) + sdio_uart_clear_mctrl(port, TIOCM_RTS); + + sdio_uart_irq(port->func); + sdio_uart_release_func(port); +} + +static void sdio_uart_unthrottle(struct tty_struct *tty) +{ + struct sdio_uart_port *port = tty->driver_data; + + if (!I_IXOFF(tty) && !(tty->termios->c_cflag & CRTSCTS)) + return; + + if (sdio_uart_claim_func(port) != 0) + return; + + if (I_IXOFF(tty)) { + if (port->x_char) { + port->x_char = 0; + } else { + port->x_char = START_CHAR(tty); + sdio_uart_start_tx(port); + } + } + + if (tty->termios->c_cflag & CRTSCTS) + sdio_uart_set_mctrl(port, TIOCM_RTS); + + sdio_uart_irq(port->func); + sdio_uart_release_func(port); +} + +static void sdio_uart_set_termios(struct tty_struct *tty, struct ktermios *old_termios) +{ + struct sdio_uart_port *port = tty->driver_data; + unsigned int cflag = tty->termios->c_cflag; + +#define RELEVANT_IFLAG(iflag) ((iflag) & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK)) + + if ((cflag ^ old_termios->c_cflag) == 0 && + RELEVANT_IFLAG(tty->termios->c_iflag ^ old_termios->c_iflag) == 0) + return; + + if (sdio_uart_claim_func(port) != 0) + return; + + sdio_uart_change_speed(port, tty->termios, old_termios); + + /* Handle transition to B0 status */ + if ((old_termios->c_cflag & CBAUD) && !(cflag & CBAUD)) + sdio_uart_clear_mctrl(port, TIOCM_RTS | TIOCM_DTR); + + /* Handle transition away from B0 status */ + if (!(old_termios->c_cflag & CBAUD) && (cflag & CBAUD)) { + unsigned int mask = TIOCM_DTR; + if (!(cflag & CRTSCTS) || !test_bit(TTY_THROTTLED, &tty->flags)) + mask |= TIOCM_RTS; + sdio_uart_set_mctrl(port, mask); + } + + /* Handle turning off CRTSCTS */ + if ((old_termios->c_cflag & CRTSCTS) && !(cflag & CRTSCTS)) { + tty->hw_stopped = 0; + sdio_uart_start_tx(port); + } + + /* Handle turning on CRTSCTS */ + if (!(old_termios->c_cflag & CRTSCTS) && (cflag & CRTSCTS)) { + if (!(sdio_uart_get_mctrl(port) & TIOCM_CTS)) { + tty->hw_stopped = 1; + sdio_uart_stop_tx(port); + } + } + + sdio_uart_release_func(port); +} + +static void sdio_uart_break_ctl(struct tty_struct *tty, int break_state) +{ + struct sdio_uart_port *port = tty->driver_data; + + if (sdio_uart_claim_func(port) != 0) + return; + + if (break_state == -1) + port->lcr |= UART_LCR_SBC; + else + port->lcr &= ~UART_LCR_SBC; + sdio_out(port, UART_LCR, port->lcr); + + sdio_uart_release_func(port); +} + +static int sdio_uart_tiocmget(struct tty_struct *tty, struct file *file) +{ + struct sdio_uart_port *port = tty->driver_data; + int result; + + result = sdio_uart_claim_func(port); + if (!result) { + result = port->mctrl | sdio_uart_get_mctrl(port); + sdio_uart_release_func(port); + } + + return result; +} + +static int sdio_uart_tiocmset(struct tty_struct *tty, struct file *file, + unsigned int set, unsigned int clear) +{ + struct sdio_uart_port *port = tty->driver_data; + int result; + + result =sdio_uart_claim_func(port); + if(!result) { + sdio_uart_update_mctrl(port, set, clear); + sdio_uart_release_func(port); + } + + return result; +} + +static const struct tty_operations sdio_uart_ops = { + .open = sdio_uart_open, + .close = sdio_uart_close, + .write = sdio_uart_write, + .write_room = sdio_uart_write_room, + .chars_in_buffer = sdio_uart_chars_in_buffer, + .send_xchar = sdio_uart_send_xchar, + .throttle = sdio_uart_throttle, + .unthrottle = sdio_uart_unthrottle, + .set_termios = sdio_uart_set_termios, + .break_ctl = sdio_uart_break_ctl, + .tiocmget = sdio_uart_tiocmget, + .tiocmset = sdio_uart_tiocmset, +}; + +static struct tty_driver *sdio_uart_tty_driver; + +static int sdio_uart_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + struct sdio_uart_port *port; + int ret; + + port = kzalloc(sizeof(struct sdio_uart_port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + if (func->class == SDIO_CLASS_UART) { + printk(KERN_WARNING "%s: need info on UART class basic setup\n", + sdio_func_id(func)); + kfree(port); + return -ENOSYS; + } else if (func->class == SDIO_CLASS_GPS) { + /* + * We need tuple 0x91. It contains SUBTPL_SIOREG + * and SUBTPL_RCVCAPS. + */ + struct sdio_func_tuple *tpl; + for (tpl = func->tuples; tpl; tpl = tpl->next) { + if (tpl->code != 0x91) + continue; + if (tpl->size < 10) + continue; + if (tpl->data[1] == 0) /* SUBTPL_SIOREG */ + break; + } + if (!tpl) { + printk(KERN_WARNING + "%s: can't find tuple 0x91 subtuple 0 (SUBTPL_SIOREG) for GPS class\n", + sdio_func_id(func)); + kfree(port); + return -EINVAL; + } + printk(KERN_DEBUG "%s: Register ID = 0x%02x, Exp ID = 0x%02x\n", + sdio_func_id(func), tpl->data[2], tpl->data[3]); + port->regs_offset = (tpl->data[4] << 0) | + (tpl->data[5] << 8) | + (tpl->data[6] << 16); + printk(KERN_DEBUG "%s: regs offset = 0x%x\n", + sdio_func_id(func), port->regs_offset); + port->uartclk = tpl->data[7] * 115200; + if (port->uartclk == 0) + port->uartclk = 115200; + printk(KERN_DEBUG "%s: clk %d baudcode %u 4800-div %u\n", + sdio_func_id(func), port->uartclk, + tpl->data[7], tpl->data[8] | (tpl->data[9] << 8)); + } else { + kfree(port); + return -EINVAL; + } + + port->func = func; + sdio_set_drvdata(func, port); + + ret = sdio_uart_add_port(port); + if (ret) { + kfree(port); + } else { + struct device *dev; + dev = tty_register_device(sdio_uart_tty_driver, port->index, &func->dev); + if (IS_ERR(dev)) { + sdio_uart_port_remove(port); + ret = PTR_ERR(dev); + } + } + + return ret; +} + +static void sdio_uart_remove(struct sdio_func *func) +{ + struct sdio_uart_port *port = sdio_get_drvdata(func); + + tty_unregister_device(sdio_uart_tty_driver, port->index); + sdio_uart_port_remove(port); +} + +static const struct sdio_device_id sdio_uart_ids[] = { + { SDIO_DEVICE_CLASS(SDIO_CLASS_UART) }, + { SDIO_DEVICE_CLASS(SDIO_CLASS_GPS) }, + { /* end: all zeroes */ }, +}; + +MODULE_DEVICE_TABLE(sdio, sdio_uart_ids); + +static struct sdio_driver sdio_uart_driver = { + .probe = sdio_uart_probe, + .remove = sdio_uart_remove, + .name = "sdio_uart", + .id_table = sdio_uart_ids, +}; + +static int __init sdio_uart_init(void) +{ + int ret; + struct tty_driver *tty_drv; + + sdio_uart_tty_driver = tty_drv = alloc_tty_driver(UART_NR); + if (!tty_drv) + return -ENOMEM; + + tty_drv->owner = THIS_MODULE; + tty_drv->driver_name = "sdio_uart"; + tty_drv->name = "ttySDIO"; + tty_drv->major = 0; /* dynamically allocated */ + tty_drv->minor_start = 0; + tty_drv->type = TTY_DRIVER_TYPE_SERIAL; + tty_drv->subtype = SERIAL_TYPE_NORMAL; + tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; + tty_drv->init_termios = tty_std_termios; + tty_drv->init_termios.c_cflag = B4800 | CS8 | CREAD | HUPCL | CLOCAL; + tty_set_operations(tty_drv, &sdio_uart_ops); + + ret = tty_register_driver(tty_drv); + if (ret) + goto err1; + + ret = sdio_register_driver(&sdio_uart_driver); + if (ret) + goto err2; + + return 0; + +err2: + tty_unregister_driver(tty_drv); +err1: + put_tty_driver(tty_drv); + return ret; +} + +static void __exit sdio_uart_exit(void) +{ + sdio_unregister_driver(&sdio_uart_driver); + tty_unregister_driver(sdio_uart_tty_driver); + put_tty_driver(sdio_uart_tty_driver); +} + +module_init(sdio_uart_init); +module_exit(sdio_uart_exit); + +MODULE_AUTHOR("Nicolas Pitre"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 5ed334a1f8caaae98806d572f78c5802975ea20f Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 4 Jul 2007 23:40:34 -0400 Subject: sdio: add /proc interface to sdio_uart driver This mimics what the serial_core does. Useful for diagnostics. Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman --- drivers/mmc/card/sdio_uart.c | 62 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/card/sdio_uart.c b/drivers/mmc/card/sdio_uart.c index e4d9e85ff58..190120d1449 100644 --- a/drivers/mmc/card/sdio_uart.c +++ b/drivers/mmc/card/sdio_uart.c @@ -913,6 +913,67 @@ static int sdio_uart_tiocmset(struct tty_struct *tty, struct file *file, return result; } +static int sdio_uart_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int i, len = 0; + off_t begin = 0; + + len += sprintf(page, "serinfo:1.0 driver%s%s revision:%s\n", + "", "", ""); + for (i = 0; i < UART_NR && len < PAGE_SIZE - 96; i++) { + struct sdio_uart_port *port = sdio_uart_port_get(i); + if (port) { + len += sprintf(page+len, "%d: uart:SDIO", i); + if(capable(CAP_SYS_ADMIN)) { + len += sprintf(page + len, " tx:%d rx:%d", + port->icount.tx, port->icount.rx); + if (port->icount.frame) + len += sprintf(page + len, " fe:%d", + port->icount.frame); + if (port->icount.parity) + len += sprintf(page + len, " pe:%d", + port->icount.parity); + if (port->icount.brk) + len += sprintf(page + len, " brk:%d", + port->icount.brk); + if (port->icount.overrun) + len += sprintf(page + len, " oe:%d", + port->icount.overrun); + if (port->icount.cts) + len += sprintf(page + len, " cts:%d", + port->icount.cts); + if (port->icount.dsr) + len += sprintf(page + len, " dsr:%d", + port->icount.dsr); + if (port->icount.rng) + len += sprintf(page + len, " rng:%d", + port->icount.rng); + if (port->icount.dcd) + len += sprintf(page + len, " dcd:%d", + port->icount.dcd); + } + strcat(page, "\n"); + len++; + sdio_uart_port_put(port); + } + + if (len + begin > off + count) + goto done; + if (len + begin < off) { + begin += len; + len = 0; + } + } + *eof = 1; + +done: + if (off >= len + begin) + return 0; + *start = page + (off - begin); + return (count < begin + len - off) ? count : (begin + len - off); +} + static const struct tty_operations sdio_uart_ops = { .open = sdio_uart_open, .close = sdio_uart_close, @@ -926,6 +987,7 @@ static const struct tty_operations sdio_uart_ops = { .break_ctl = sdio_uart_break_ctl, .tiocmget = sdio_uart_tiocmget, .tiocmset = sdio_uart_tiocmset, + .read_proc = sdio_uart_read_proc, }; static struct tty_driver *sdio_uart_tty_driver; -- cgit v1.2.3 From 112c9db91ee6bf19eca7cbb6854be3127381c229 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 6 Jul 2007 13:35:01 +0200 Subject: sdio: support IO_RW_EXTENDED Support the multi-byte transfer operation, including handlers for common operations like writel()/readl(). Signed-off-by: Pierre Ossman --- drivers/mmc/core/sdio_io.c | 184 ++++++++++++++++++++++++++++++++++++++++++ drivers/mmc/core/sdio_ops.c | 57 +++++++++++++ drivers/mmc/core/sdio_ops.h | 2 + include/linux/mmc/sdio.h | 12 +++ include/linux/mmc/sdio_func.h | 20 +++++ 5 files changed, 275 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/core/sdio_io.c b/drivers/mmc/core/sdio_io.c index eb6c20935ce..ecdb77242e9 100644 --- a/drivers/mmc/core/sdio_io.c +++ b/drivers/mmc/core/sdio_io.c @@ -196,3 +196,187 @@ void sdio_writeb(struct sdio_func *func, unsigned char b, unsigned int addr, } EXPORT_SYMBOL_GPL(sdio_writeb); +/** + * sdio_memcpy_fromio - read a chunk of memory from a SDIO function + * @func: SDIO function to access + * @dst: buffer to store the data + * @addr: address to begin reading from + * @count: number of bytes to read + * + * Reads up to 512 bytes from the address space of a given SDIO + * function. Return value indicates if the transfer succeeded or + * not. + */ +int sdio_memcpy_fromio(struct sdio_func *func, void *dst, + unsigned int addr, int count) +{ + return mmc_io_rw_extended(func->card, 0, func->num, addr, 0, dst, + count); +} +EXPORT_SYMBOL_GPL(sdio_memcpy_fromio); + +/** + * sdio_memcpy_toio - write a chunk of memory to a SDIO function + * @func: SDIO function to access + * @addr: address to start writing to + * @src: buffer that contains the data to write + * @count: number of bytes to write + * + * Writes up to 512 bytes to the address space of a given SDIO + * function. Return value indicates if the transfer succeeded or + * not. + */ +int sdio_memcpy_toio(struct sdio_func *func, unsigned int addr, + void *src, int count) +{ + return mmc_io_rw_extended(func->card, 1, func->num, addr, 0, src, + count); +} +EXPORT_SYMBOL_GPL(sdio_memcpy_toio); + +/** + * sdio_readsb - read from a FIFO on a SDIO function + * @func: SDIO function to access + * @dst: buffer to store the data + * @addr: address of (single byte) FIFO + * @count: number of bytes to read + * + * Reads up to 512 bytes from the specified FIFO of a given SDIO + * function. Return value indicates if the transfer succeeded or + * not. + */ +int sdio_readsb(struct sdio_func *func, void *dst, unsigned int addr, + int count) +{ + return mmc_io_rw_extended(func->card, 0, func->num, addr, 1, dst, + count); +} + +EXPORT_SYMBOL_GPL(sdio_readsb); + +/** + * sdio_writesb - write to a FIFO of a SDIO function + * @func: SDIO function to access + * @addr: address of (single byte) FIFO + * @src: buffer that contains the data to write + * @count: number of bytes to write + * + * Writes up to 512 bytes to the specified FIFO of a given SDIO + * function. Return value indicates if the transfer succeeded or + * not. + */ +int sdio_writesb(struct sdio_func *func, unsigned int addr, void *src, + int count) +{ + return mmc_io_rw_extended(func->card, 1, func->num, addr, 1, src, + count); +} +EXPORT_SYMBOL_GPL(sdio_writesb); + +/** + * sdio_readw - read a 16 bit integer from a SDIO function + * @func: SDIO function to access + * @addr: address to read + * @err_ret: optional status value from transfer + * + * Reads a 16 bit integer from the address space of a given SDIO + * function. If there is a problem reading the address, 0xffff + * is returned and @err_ret will contain the error code. + */ +unsigned short sdio_readw(struct sdio_func *func, unsigned int addr, + int *err_ret) +{ + int ret; + + if (err_ret) + *err_ret = 0; + + ret = sdio_memcpy_fromio(func, func->tmpbuf, addr, 2); + if (ret) { + if (err_ret) + *err_ret = ret; + return 0xFFFF; + } + + return le16_to_cpu(*(u16*)func->tmpbuf); +} +EXPORT_SYMBOL_GPL(sdio_readw); + +/** + * sdio_writew - write a 16 bit integer to a SDIO function + * @func: SDIO function to access + * @b: integer to write + * @addr: address to write to + * @err_ret: optional status value from transfer + * + * Writes a 16 bit integer to the address space of a given SDIO + * function. @err_ret will contain the status of the actual + * transfer. + */ +void sdio_writew(struct sdio_func *func, unsigned short b, unsigned int addr, + int *err_ret) +{ + int ret; + + *(u16*)func->tmpbuf = cpu_to_le16(b); + + ret = sdio_memcpy_toio(func, addr, func->tmpbuf, 2); + if (err_ret) + *err_ret = ret; +} +EXPORT_SYMBOL_GPL(sdio_writew); + +/** + * sdio_readl - read a 32 bit integer from a SDIO function + * @func: SDIO function to access + * @addr: address to read + * @err_ret: optional status value from transfer + * + * Reads a 32 bit integer from the address space of a given SDIO + * function. If there is a problem reading the address, + * 0xffffffff is returned and @err_ret will contain the error + * code. + */ +unsigned long sdio_readl(struct sdio_func *func, unsigned int addr, + int *err_ret) +{ + int ret; + + if (err_ret) + *err_ret = 0; + + ret = sdio_memcpy_fromio(func, func->tmpbuf, addr, 4); + if (ret) { + if (err_ret) + *err_ret = ret; + return 0xFFFFFFFF; + } + + return le32_to_cpu(*(u32*)func->tmpbuf); +} +EXPORT_SYMBOL_GPL(sdio_readl); + +/** + * sdio_writel - write a 32 bit integer to a SDIO function + * @func: SDIO function to access + * @b: integer to write + * @addr: address to write to + * @err_ret: optional status value from transfer + * + * Writes a 32 bit integer to the address space of a given SDIO + * function. @err_ret will contain the status of the actual + * transfer. + */ +void sdio_writel(struct sdio_func *func, unsigned long b, unsigned int addr, + int *err_ret) +{ + int ret; + + *(u32*)func->tmpbuf = cpu_to_le32(b); + + ret = sdio_memcpy_toio(func, addr, func->tmpbuf, 4); + if (err_ret) + *err_ret = ret; +} +EXPORT_SYMBOL_GPL(sdio_writel); + diff --git a/drivers/mmc/core/sdio_ops.c b/drivers/mmc/core/sdio_ops.c index 31233f7b55c..4f2c7713947 100644 --- a/drivers/mmc/core/sdio_ops.c +++ b/drivers/mmc/core/sdio_ops.c @@ -9,6 +9,9 @@ * your option) any later version. */ +#include +#include + #include #include #include @@ -84,3 +87,57 @@ int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn, return 0; } +int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn, + unsigned addr, int bang, u8 *buf, unsigned size) +{ + struct mmc_request mrq; + struct mmc_command cmd; + struct mmc_data data; + struct scatterlist sg; + + BUG_ON(!card); + BUG_ON(fn > 7); + BUG_ON(size > 512); + + memset(&mrq, 0, sizeof(struct mmc_request)); + memset(&cmd, 0, sizeof(struct mmc_command)); + memset(&data, 0, sizeof(struct mmc_data)); + + mrq.cmd = &cmd; + mrq.data = &data; + + cmd.opcode = SD_IO_RW_EXTENDED; + cmd.arg = write ? 0x80000000 : 0x00000000; + cmd.arg |= fn << 28; + cmd.arg |= bang ? 0x00000000 : 0x04000000; + cmd.arg |= addr << 9; + cmd.arg |= (size == 512) ? 0 : size; + cmd.flags = MMC_RSP_R5 | MMC_CMD_ADTC; + + data.blksz = size; + data.blocks = 1; + data.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ; + data.sg = &sg; + data.sg_len = 1; + + sg_init_one(&sg, buf, size); + + mmc_set_data_timeout(&data, card); + + mmc_wait_for_req(card->host, &mrq); + + if (cmd.error) + return cmd.error; + if (data.error) + return data.error; + + if (cmd.resp[0] & R5_ERROR) + return -EIO; + if (cmd.resp[0] & R5_FUNCTION_NUMBER) + return -EINVAL; + if (cmd.resp[0] & R5_OUT_OF_RANGE) + return -ERANGE; + + return 0; +} + diff --git a/drivers/mmc/core/sdio_ops.h b/drivers/mmc/core/sdio_ops.h index f0e9d69e5ce..1d42e4f366a 100644 --- a/drivers/mmc/core/sdio_ops.h +++ b/drivers/mmc/core/sdio_ops.h @@ -15,6 +15,8 @@ int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr); int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn, unsigned addr, u8 in, u8* out); +int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn, + unsigned addr, int bang, u8 *data, unsigned size); #endif diff --git a/include/linux/mmc/sdio.h b/include/linux/mmc/sdio.h index 56239f4aab0..9b1ec76cac3 100644 --- a/include/linux/mmc/sdio.h +++ b/include/linux/mmc/sdio.h @@ -15,6 +15,7 @@ /* SDIO commands type argument response */ #define SD_IO_SEND_OP_COND 5 /* bcr [23:0] OCR R4 */ #define SD_IO_RW_DIRECT 52 /* ac [31:0] See below R5 */ +#define SD_IO_RW_EXTENDED 53 /* adtc [31:0] See below R5 */ /* * SD_IO_RW_DIRECT argument format: @@ -26,6 +27,17 @@ * [7:0] Data */ +/* + * SD_IO_RW_EXTENDED argument format: + * + * [31] R/W flag + * [30:28] Function number + * [27] Block mode + * [26] Increment address + * [25:9] Register address + * [8:0] Byte/block count + */ + /* SDIO status in R5 Type diff --git a/include/linux/mmc/sdio_func.h b/include/linux/mmc/sdio_func.h index a8d268c9c27..af813fffc4a 100644 --- a/include/linux/mmc/sdio_func.h +++ b/include/linux/mmc/sdio_func.h @@ -48,6 +48,8 @@ struct sdio_func { unsigned int state; /* function state */ #define SDIO_STATE_PRESENT (1<<0) /* present in sysfs */ + u8 tmpbuf[4]; /* DMA:able scratch buffer */ + struct sdio_func_tuple *tuples; }; @@ -114,9 +116,27 @@ extern int sdio_release_irq(struct sdio_func *func); extern unsigned char sdio_readb(struct sdio_func *func, unsigned int addr, int *err_ret); +extern unsigned short sdio_readw(struct sdio_func *func, + unsigned int addr, int *err_ret); +extern unsigned long sdio_readl(struct sdio_func *func, + unsigned int addr, int *err_ret); + +extern int sdio_memcpy_fromio(struct sdio_func *func, void *dst, + unsigned int addr, int count); +extern int sdio_readsb(struct sdio_func *func, void *dst, + unsigned int addr, int count); extern void sdio_writeb(struct sdio_func *func, unsigned char b, unsigned int addr, int *err_ret); +extern void sdio_writew(struct sdio_func *func, unsigned short b, + unsigned int addr, int *err_ret); +extern void sdio_writel(struct sdio_func *func, unsigned long b, + unsigned int addr, int *err_ret); + +extern int sdio_memcpy_toio(struct sdio_func *func, unsigned int addr, + void *src, int count); +extern int sdio_writesb(struct sdio_func *func, unsigned int addr, + void *src, int count); #endif -- cgit v1.2.3 From 17b759aff916b4d02721e75ce5ed82b1903e5bd6 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Tue, 24 Jul 2007 02:09:39 -0400 Subject: sdio: add interface for host side SDIO interrupt reporting Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman --- drivers/mmc/core/host.c | 3 +++ drivers/mmc/core/sdio_irq.c | 8 +++++++- include/linux/mmc/host.h | 8 ++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 2c7ce8f43a9..37b761891d6 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -100,6 +100,9 @@ int mmc_add_host(struct mmc_host *host) { int err; + WARN_ON((host->caps & MMC_CAP_SDIO_IRQ) && + !host->ops->enable_sdio_irq); + if (!idr_pre_get(&mmc_host_idr, GFP_KERNEL)) return -ENOMEM; diff --git a/drivers/mmc/core/sdio_irq.c b/drivers/mmc/core/sdio_irq.c index 01922d29241..01daee934d1 100644 --- a/drivers/mmc/core/sdio_irq.c +++ b/drivers/mmc/core/sdio_irq.c @@ -70,7 +70,8 @@ static int sdio_irq_thread(void *_host) * asynchronous notification of pending SDIO card interrupts * hence we poll for them in that case. */ - period = msecs_to_jiffies(10); + period = (host->caps & MMC_CAP_SDIO_IRQ) ? + MAX_SCHEDULE_TIMEOUT : msecs_to_jiffies(10); pr_debug("%s: IRQ thread started (poll period = %lu jiffies)\n", mmc_hostname(host), period); @@ -104,11 +105,16 @@ static int sdio_irq_thread(void *_host) ssleep(1); set_task_state(current, TASK_INTERRUPTIBLE); + if (host->caps & MMC_CAP_SDIO_IRQ) + host->ops->enable_sdio_irq(host, 1); if (!kthread_should_stop()) schedule_timeout(period); set_task_state(current, TASK_RUNNING); } while (!kthread_should_stop()); + if (host->caps & MMC_CAP_SDIO_IRQ) + host->ops->enable_sdio_irq(host, 0); + pr_debug("%s: IRQ thread exiting with code %d\n", mmc_hostname(host), ret); diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 00dc1809494..3fd197962f7 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -51,6 +51,7 @@ struct mmc_host_ops { void (*request)(struct mmc_host *host, struct mmc_request *req); void (*set_ios)(struct mmc_host *host, struct mmc_ios *ios); int (*get_ro)(struct mmc_host *host); + void (*enable_sdio_irq)(struct mmc_host *host, int enable); }; struct mmc_card; @@ -89,6 +90,7 @@ struct mmc_host { #define MMC_CAP_MULTIWRITE (1 << 1) /* Can accurately report bytes sent to card on error */ #define MMC_CAP_MMC_HIGHSPEED (1 << 2) /* Can do MMC high-speed timing */ #define MMC_CAP_SD_HIGHSPEED (1 << 3) /* Can do SD high-speed timing */ +#define MMC_CAP_SDIO_IRQ (1 << 4) /* Can signal pending SDIO IRQs */ /* host specific block data */ unsigned int max_seg_size; /* see blk_queue_max_segment_size */ @@ -150,5 +152,11 @@ extern int mmc_resume_host(struct mmc_host *); extern void mmc_detect_change(struct mmc_host *, unsigned long delay); extern void mmc_request_done(struct mmc_host *, struct mmc_request *); +static inline void mmc_signal_sdio_irq(struct mmc_host *host) +{ + host->ops->enable_sdio_irq(host, 0); + wake_up_process(host->sdio_irq_thread); +} + #endif -- cgit v1.2.3 From 22bfc979d38f57d5b10d141990175d8fc47f6775 Mon Sep 17 00:00:00 2001 From: Adrian Bunk Date: Sun, 29 Jul 2007 16:58:09 +0200 Subject: make struct sdio_dev_attrs[] static On Wed, Jul 25, 2007 at 04:03:04AM -0700, Andrew Morton wrote: >... > Changes since 2.6.22-rc6-mm1: >... > git-mmc.patch >... > git trees >... sdio_dev_attrs[] can become static. Signed-off-by: Adrian Bunk Signed-off-by: Pierre Ossman --- drivers/mmc/core/sdio_bus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/mmc/core/sdio_bus.c b/drivers/mmc/core/sdio_bus.c index 24072445491..d229020db4a 100644 --- a/drivers/mmc/core/sdio_bus.c +++ b/drivers/mmc/core/sdio_bus.c @@ -46,7 +46,7 @@ static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, func->class, func->vendor, func->device); } -struct device_attribute sdio_dev_attrs[] = { +static struct device_attribute sdio_dev_attrs[] = { __ATTR_RO(class), __ATTR_RO(vendor), __ATTR_RO(device), -- cgit v1.2.3 From 6db5020e7386ddf17378f91eb8c445433e5b07cd Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 30 Jul 2007 17:15:07 +0200 Subject: sdio: change clock speed Change clock speed to the highest supported by the card. Signed-off-by: Pierre Ossman --- drivers/mmc/core/sdio.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 1fb36a34046..62df8e17758 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -293,6 +293,12 @@ int mmc_attach_sdio(struct mmc_host *host, u32 ocr) if (err) goto remove; + /* + * No support for high-speed yet, so just set + * the card's maximum speed. + */ + mmc_set_clock(host, card->cis.max_dtr); + /* * Initialize (but don't add) all present functions. */ -- cgit v1.2.3 From 4ff6471c028a9885e8f09a000d87694f81190ab9 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 30 Jul 2007 18:23:53 +0200 Subject: sdio: enable wide bus mode Enable 4-bit data bus mode, according to host and card capabilities. Signed-off-by: Pierre Ossman --- drivers/mmc/core/sdio.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 62df8e17758..48c465a8e34 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -138,6 +138,32 @@ out: return ret; } +static int sdio_enable_wide(struct mmc_card *card) +{ + int ret; + u8 ctrl; + + if (!(card->host->caps & MMC_CAP_4_BIT_DATA)) + return 0; + + if (card->cccr.low_speed && !card->cccr.wide_bus) + return 0; + + ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_IF, 0, &ctrl); + if (ret) + return ret; + + ctrl |= SDIO_BUS_WIDTH_4BIT; + + ret = mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_IF, ctrl, NULL); + if (ret) + return ret; + + mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4); + + return 0; +} + /* * Host is being removed. Free up the current card. */ @@ -299,6 +325,13 @@ int mmc_attach_sdio(struct mmc_host *host, u32 ocr) */ mmc_set_clock(host, card->cis.max_dtr); + /* + * Switch to wider bus (if supported). + */ + err = sdio_enable_wide(card); + if (err) + goto remove; + /* * Initialize (but don't add) all present functions. */ -- cgit v1.2.3 From ce252edd869ba1fee6a9a6f83e20f349d4c4d669 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 7 Aug 2007 14:06:18 +0200 Subject: mmc: fix incorrect divisor in debug output Signed-off-by: Pierre Ossman --- drivers/mmc/core/core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index d1e4b0849e3..07c03cb677b 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -121,7 +121,7 @@ mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) "tsac %d ms nsac %d\n", mmc_hostname(host), mrq->data->blksz, mrq->data->blocks, mrq->data->flags, - mrq->data->timeout_ns / 10000000, + mrq->data->timeout_ns / 1000000, mrq->data->timeout_clks); } -- cgit v1.2.3 From e6f918bf39773d712ab5b457bff54ade3bda0cb1 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 7 Aug 2007 14:11:55 +0200 Subject: mmc: fix sdio timeout calculation SDIO doesn't have a CSD so it uses different timeout values than SD memory. Signed-off-by: Pierre Ossman --- drivers/mmc/core/core.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 07c03cb677b..9d29bed820a 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -231,6 +231,15 @@ void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card) { unsigned int mult; + /* + * SDIO cards only define an upper 1 s limit on access. + */ + if (mmc_card_sdio(card)) { + data->timeout_ns = 1000000000; + data->timeout_clks = 0; + return; + } + /* * SD cards use a 100 multiplier rather than 10 */ -- cgit v1.2.3 From 7616ee95f27a04fd5a6434e9ef4a82cec4b2807c Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Wed, 8 Aug 2007 14:23:05 +0100 Subject: sdio: add SDIO_FBR_BASE(f) macro Signed-off-by: David Vrabel Signed-off-by: Pierre Ossman --- drivers/mmc/core/sdio.c | 4 ++-- drivers/mmc/core/sdio_cis.c | 2 +- include/linux/mmc/sdio.h | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 48c465a8e34..58cf36e4467 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -30,7 +30,7 @@ static int sdio_read_fbr(struct sdio_func *func) unsigned char data; ret = mmc_io_rw_direct(func->card, 0, 0, - func->num * 0x100 + SDIO_FBR_STD_IF, 0, &data); + SDIO_FBR_BASE(func->num) + SDIO_FBR_STD_IF, 0, &data); if (ret) goto out; @@ -38,7 +38,7 @@ static int sdio_read_fbr(struct sdio_func *func) if (data == 0x0f) { ret = mmc_io_rw_direct(func->card, 0, 0, - func->num * 0x100 + SDIO_FBR_STD_IF_EXT, 0, &data); + SDIO_FBR_BASE(func->num) + SDIO_FBR_STD_IF_EXT, 0, &data); if (ret) goto out; } diff --git a/drivers/mmc/core/sdio_cis.c b/drivers/mmc/core/sdio_cis.c index ec806a1229b..d050c40cf04 100644 --- a/drivers/mmc/core/sdio_cis.c +++ b/drivers/mmc/core/sdio_cis.c @@ -145,7 +145,7 @@ static int sdio_read_cis(struct mmc_card *card, struct sdio_func *func) fn = 0; ret = mmc_io_rw_direct(card, 0, 0, - fn * 0x100 + SDIO_FBR_CIS + i, 0, &x); + SDIO_FBR_BASE(fn) + SDIO_FBR_CIS + i, 0, &x); if (ret) return ret; ptr |= x << (i * 8); diff --git a/include/linux/mmc/sdio.h b/include/linux/mmc/sdio.h index 9b1ec76cac3..47ba464f517 100644 --- a/include/linux/mmc/sdio.h +++ b/include/linux/mmc/sdio.h @@ -132,6 +132,8 @@ * Function Basic Registers (FBR) */ +#define SDIO_FBR_BASE(f) ((f) * 0x100) /* base of function f's FBRs */ + #define SDIO_FBR_STD_IF 0x00 #define SDIO_FBR_SUPPORTS_CSA 0x40 /* supports Code Storage Area */ -- cgit v1.2.3 From 9a08f82b3cc522f727ace580a2aaee5402435bc8 Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Wed, 8 Aug 2007 14:23:48 +0100 Subject: sdio: set the functions' block size Before a driver is probed, set the function's block size to the default so the driver is sure the block size is something sensible and it needn't explicitly set it. The default block size is the largest that's supported by both the card and the host, with a maximum of 512 to ensure aribitrarily sized transfer use the optimal (least) number of commands. See http://lkml.org/lkml/2007/8/7/150 for reasons for the block size choice. Signed-off-by: David Vrabel Signed-off-by: Pierre Ossman --- drivers/mmc/core/sdio_bus.c | 9 ++++++++ drivers/mmc/core/sdio_cis.c | 2 +- drivers/mmc/core/sdio_io.c | 49 +++++++++++++++++++++++++++++++++++++++++++ include/linux/mmc/sdio_func.h | 5 ++++- 4 files changed, 63 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/core/sdio_bus.c b/drivers/mmc/core/sdio_bus.c index d229020db4a..fcb13fb0daa 100644 --- a/drivers/mmc/core/sdio_bus.c +++ b/drivers/mmc/core/sdio_bus.c @@ -128,11 +128,20 @@ static int sdio_bus_probe(struct device *dev) struct sdio_driver *drv = to_sdio_driver(dev->driver); struct sdio_func *func = dev_to_sdio_func(dev); const struct sdio_device_id *id; + int ret; id = sdio_match_device(func, drv); if (!id) return -ENODEV; + /* Set the default block size so the driver is sure it's something + * sensible. */ + sdio_claim_host(func); + ret = sdio_set_block_size(func, 0); + sdio_release_host(func); + if (ret) + return ret; + return drv->probe(func, id); } diff --git a/drivers/mmc/core/sdio_cis.c b/drivers/mmc/core/sdio_cis.c index d050c40cf04..1d03f12bbb3 100644 --- a/drivers/mmc/core/sdio_cis.c +++ b/drivers/mmc/core/sdio_cis.c @@ -79,7 +79,7 @@ static int cistpl_funce_func(struct sdio_func *func, return -EINVAL; /* TPLFE_MAX_BLK_SIZE */ - func->blksize = buf[12] | (buf[13] << 8); + func->max_blksize = buf[12] | (buf[13] << 8); return 0; } diff --git a/drivers/mmc/core/sdio_io.c b/drivers/mmc/core/sdio_io.c index ecdb77242e9..c2bad1195e3 100644 --- a/drivers/mmc/core/sdio_io.c +++ b/drivers/mmc/core/sdio_io.c @@ -140,6 +140,55 @@ err: } EXPORT_SYMBOL_GPL(sdio_disable_func); +/** + * sdio_set_block_size - set the block size of an SDIO function + * @func: SDIO function to change + * @blksz: new block size or 0 to use the default. + * + * The default block size is the largest supported by both the function + * and the host, with a maximum of 512 to ensure that arbitrarily sized + * data transfer use the optimal (least) number of commands. + * + * A driver may call this to override the default block size set by the + * core. This can be used to set a block size greater than the maximum + * that reported by the card; it is the driver's responsibility to ensure + * it uses a value that the card supports. + * + * Returns 0 on success, -EINVAL if the host does not support the + * requested block size, or -EIO (etc.) if one of the resultant FBR block + * size register writes failed. + * + */ +int sdio_set_block_size(struct sdio_func *func, unsigned blksz) +{ + int ret; + + if (blksz > func->card->host->max_blk_size) + return -EINVAL; + + if (blksz == 0) { + blksz = min(min( + func->max_blksize, + func->card->host->max_blk_size), + 512u); + } + + ret = mmc_io_rw_direct(func->card, 1, 0, + SDIO_FBR_BASE(func->num) + SDIO_FBR_BLKSIZE, + blksz & 0xff, NULL); + if (ret) + return ret; + ret = mmc_io_rw_direct(func->card, 1, 0, + SDIO_FBR_BASE(func->num) + SDIO_FBR_BLKSIZE + 1, + (blksz >> 8) & 0xff, NULL); + if (ret) + return ret; + func->cur_blksize = blksz; + return 0; +} + +EXPORT_SYMBOL_GPL(sdio_set_block_size); + /** * sdio_readb - read a single byte from a SDIO function * @func: SDIO function to access diff --git a/include/linux/mmc/sdio_func.h b/include/linux/mmc/sdio_func.h index af813fffc4a..f05757984e8 100644 --- a/include/linux/mmc/sdio_func.h +++ b/include/linux/mmc/sdio_func.h @@ -43,7 +43,8 @@ struct sdio_func { unsigned short vendor; /* vendor id */ unsigned short device; /* device id */ - unsigned short blksize; /* maximum block size */ + unsigned max_blksize; /* maximum block size */ + unsigned cur_blksize; /* current block size */ unsigned int state; /* function state */ #define SDIO_STATE_PRESENT (1<<0) /* present in sysfs */ @@ -111,6 +112,8 @@ extern void sdio_release_host(struct sdio_func *func); extern int sdio_enable_func(struct sdio_func *func); extern int sdio_disable_func(struct sdio_func *func); +extern int sdio_set_block_size(struct sdio_func *func, unsigned blksz); + extern int sdio_claim_irq(struct sdio_func *func, sdio_irq_handler_t *handler); extern int sdio_release_irq(struct sdio_func *func); -- cgit v1.2.3 From eb6594689226663968ef0a9fd71ec5e1e4e04f9c Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Wed, 8 Aug 2007 14:24:21 +0100 Subject: sdio: extend sdio_readsb() and friends to handle any length of buffer Extend sdio_readsb(), sdio_writesb(), sdio_memcpy_fromio(), and sdio_memcpy_toio() to handle any length of buffer by splitting the transfer into several IO_RW_EXTENDED commands. Typically, a transfer would be split into a single block mode transfer followed by a byte mode transfer for the remainder but we also handle lack of block mode support and the block size being greater than 512 (the maximum byte mode transfer size). host->max_seg_size <= host->max_req_size so there's no need to check both when determining the maximum data size for a single command. Signed-off-by: David Vrabel Signed-off-by: Pierre Ossman --- drivers/mmc/core/sdio_io.c | 93 +++++++++++++++++++++++++++++++++++---------- drivers/mmc/core/sdio_ops.c | 19 +++++---- drivers/mmc/core/sdio_ops.h | 2 +- 3 files changed, 86 insertions(+), 28 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/core/sdio_io.c b/drivers/mmc/core/sdio_io.c index c2bad1195e3..34b085d4024 100644 --- a/drivers/mmc/core/sdio_io.c +++ b/drivers/mmc/core/sdio_io.c @@ -189,6 +189,67 @@ int sdio_set_block_size(struct sdio_func *func, unsigned blksz) EXPORT_SYMBOL_GPL(sdio_set_block_size); +/* Split an arbitrarily sized data transfer into several + * IO_RW_EXTENDED commands. */ +static int sdio_io_rw_ext_helper(struct sdio_func *func, int write, + unsigned addr, int incr_addr, u8 *buf, unsigned size) +{ + unsigned remainder = size; + unsigned max_blocks; + int ret; + + /* Do the bulk of the transfer using block mode (if supported). */ + if (func->card->cccr.multi_block) { + /* Blocks per command is limited by host count, host transfer + * size (we only use a single sg entry) and the maximum for + * IO_RW_EXTENDED of 511 blocks. */ + max_blocks = min(min( + func->card->host->max_blk_count, + func->card->host->max_seg_size / func->cur_blksize), + 511u); + + while (remainder > func->cur_blksize) { + unsigned blocks; + + blocks = remainder / func->cur_blksize; + if (blocks > max_blocks) + blocks = max_blocks; + size = blocks * func->cur_blksize; + + ret = mmc_io_rw_extended(func->card, write, + func->num, addr, incr_addr, buf, + blocks, func->cur_blksize); + if (ret) + return ret; + + remainder -= size; + buf += size; + if (incr_addr) + addr += size; + } + } + + /* Write the remainder using byte mode. */ + while (remainder > 0) { + size = remainder; + if (size > func->cur_blksize) + size = func->cur_blksize; + if (size > 512) + size = 512; /* maximum size for byte mode */ + + ret = mmc_io_rw_extended(func->card, write, func->num, addr, + incr_addr, buf, 1, size); + if (ret) + return ret; + + remainder -= size; + buf += size; + if (incr_addr) + addr += size; + } + return 0; +} + /** * sdio_readb - read a single byte from a SDIO function * @func: SDIO function to access @@ -252,15 +313,13 @@ EXPORT_SYMBOL_GPL(sdio_writeb); * @addr: address to begin reading from * @count: number of bytes to read * - * Reads up to 512 bytes from the address space of a given SDIO - * function. Return value indicates if the transfer succeeded or - * not. + * Reads from the address space of a given SDIO function. Return + * value indicates if the transfer succeeded or not. */ int sdio_memcpy_fromio(struct sdio_func *func, void *dst, unsigned int addr, int count) { - return mmc_io_rw_extended(func->card, 0, func->num, addr, 0, dst, - count); + return sdio_io_rw_ext_helper(func, 0, addr, 1, dst, count); } EXPORT_SYMBOL_GPL(sdio_memcpy_fromio); @@ -271,15 +330,13 @@ EXPORT_SYMBOL_GPL(sdio_memcpy_fromio); * @src: buffer that contains the data to write * @count: number of bytes to write * - * Writes up to 512 bytes to the address space of a given SDIO - * function. Return value indicates if the transfer succeeded or - * not. + * Writes to the address space of a given SDIO function. Return + * value indicates if the transfer succeeded or not. */ int sdio_memcpy_toio(struct sdio_func *func, unsigned int addr, void *src, int count) { - return mmc_io_rw_extended(func->card, 1, func->num, addr, 0, src, - count); + return sdio_io_rw_ext_helper(func, 1, addr, 1, src, count); } EXPORT_SYMBOL_GPL(sdio_memcpy_toio); @@ -290,15 +347,13 @@ EXPORT_SYMBOL_GPL(sdio_memcpy_toio); * @addr: address of (single byte) FIFO * @count: number of bytes to read * - * Reads up to 512 bytes from the specified FIFO of a given SDIO - * function. Return value indicates if the transfer succeeded or - * not. + * Reads from the specified FIFO of a given SDIO function. Return + * value indicates if the transfer succeeded or not. */ int sdio_readsb(struct sdio_func *func, void *dst, unsigned int addr, int count) { - return mmc_io_rw_extended(func->card, 0, func->num, addr, 1, dst, - count); + return sdio_io_rw_ext_helper(func, 0, addr, 0, dst, count); } EXPORT_SYMBOL_GPL(sdio_readsb); @@ -310,15 +365,13 @@ EXPORT_SYMBOL_GPL(sdio_readsb); * @src: buffer that contains the data to write * @count: number of bytes to write * - * Writes up to 512 bytes to the specified FIFO of a given SDIO - * function. Return value indicates if the transfer succeeded or - * not. + * Writes to the specified FIFO of a given SDIO function. Return + * value indicates if the transfer succeeded or not. */ int sdio_writesb(struct sdio_func *func, unsigned int addr, void *src, int count) { - return mmc_io_rw_extended(func->card, 1, func->num, addr, 1, src, - count); + return sdio_io_rw_ext_helper(func, 1, addr, 0, src, count); } EXPORT_SYMBOL_GPL(sdio_writesb); diff --git a/drivers/mmc/core/sdio_ops.c b/drivers/mmc/core/sdio_ops.c index 4f2c7713947..98e20532452 100644 --- a/drivers/mmc/core/sdio_ops.c +++ b/drivers/mmc/core/sdio_ops.c @@ -88,7 +88,7 @@ int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn, } int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn, - unsigned addr, int bang, u8 *buf, unsigned size) + unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz) { struct mmc_request mrq; struct mmc_command cmd; @@ -97,7 +97,9 @@ int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn, BUG_ON(!card); BUG_ON(fn > 7); - BUG_ON(size > 512); + BUG_ON(blocks == 1 && blksz > 512); + WARN_ON(blocks == 0); + WARN_ON(blksz == 0); memset(&mrq, 0, sizeof(struct mmc_request)); memset(&cmd, 0, sizeof(struct mmc_command)); @@ -109,18 +111,21 @@ int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn, cmd.opcode = SD_IO_RW_EXTENDED; cmd.arg = write ? 0x80000000 : 0x00000000; cmd.arg |= fn << 28; - cmd.arg |= bang ? 0x00000000 : 0x04000000; + cmd.arg |= incr_addr ? 0x04000000 : 0x00000000; cmd.arg |= addr << 9; - cmd.arg |= (size == 512) ? 0 : size; + if (blocks == 1 && blksz <= 512) + cmd.arg |= (blksz == 512) ? 0 : blksz; /* byte mode */ + else + cmd.arg |= 0x08000000 | blocks; /* block mode */ cmd.flags = MMC_RSP_R5 | MMC_CMD_ADTC; - data.blksz = size; - data.blocks = 1; + data.blksz = blksz; + data.blocks = blocks; data.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ; data.sg = &sg; data.sg_len = 1; - sg_init_one(&sg, buf, size); + sg_init_one(&sg, buf, blksz * blocks); mmc_set_data_timeout(&data, card); diff --git a/drivers/mmc/core/sdio_ops.h b/drivers/mmc/core/sdio_ops.h index 1d42e4f366a..e2e74b0d17d 100644 --- a/drivers/mmc/core/sdio_ops.h +++ b/drivers/mmc/core/sdio_ops.h @@ -16,7 +16,7 @@ int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr); int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn, unsigned addr, u8 in, u8* out); int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn, - unsigned addr, int bang, u8 *data, unsigned size); + unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz); #endif -- cgit v1.2.3 From 9f2fcf99394b34769e3243a7f42a0ba8d21fc774 Mon Sep 17 00:00:00 2001 From: Mariusz Kozlowski Date: Wed, 1 Aug 2007 00:05:24 +0200 Subject: sdio: kmalloc + memset conversion to kzalloc Signed-off-by: Mariusz Kozlowski Signed-off-by: Pierre Ossman --- drivers/mmc/core/sdio_bus.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/core/sdio_bus.c b/drivers/mmc/core/sdio_bus.c index fcb13fb0daa..683d9174010 100644 --- a/drivers/mmc/core/sdio_bus.c +++ b/drivers/mmc/core/sdio_bus.c @@ -221,12 +221,10 @@ struct sdio_func *sdio_alloc_func(struct mmc_card *card) { struct sdio_func *func; - func = kmalloc(sizeof(struct sdio_func), GFP_KERNEL); + func = kzalloc(sizeof(struct sdio_func), GFP_KERNEL); if (!func) return ERR_PTR(-ENOMEM); - memset(func, 0, sizeof(struct sdio_func)); - func->card = card; device_initialize(&func->dev); -- cgit v1.2.3 From d84075c8aed771d47d7ac6e96b098559da361c25 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 9 Aug 2007 13:23:56 +0200 Subject: mmc: replace BUG_ON with WARN_ON Replace all cases of BUG_ON with WARN_ON where there is a chance (with varying degrees of slim) that the kernel can continue without incidence. Signed-off-by: Pierre Ossman --- drivers/mmc/core/core.c | 12 ++++++------ drivers/mmc/core/mmc.c | 4 ++-- drivers/mmc/core/sd.c | 6 ++---- drivers/mmc/core/sdio.c | 2 +- drivers/mmc/core/sdio_irq.c | 4 ++-- 5 files changed, 13 insertions(+), 15 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 9d29bed820a..3bebd3b55dc 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -202,7 +202,7 @@ int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries { struct mmc_request mrq; - BUG_ON(!host->claimed); + WARN_ON(!host->claimed); memset(&mrq, 0, sizeof(struct mmc_request)); @@ -333,7 +333,7 @@ void mmc_release_host(struct mmc_host *host) { unsigned long flags; - BUG_ON(!host->claimed); + WARN_ON(!host->claimed); spin_lock_irqsave(&host->lock, flags); host->claimed = 0; @@ -531,7 +531,7 @@ void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops) BUG_ON(!host); BUG_ON(!ops); - BUG_ON(!host->claimed); + WARN_ON(!host->claimed); spin_lock_irqsave(&host->lock, flags); @@ -555,8 +555,8 @@ void mmc_detach_bus(struct mmc_host *host) BUG_ON(!host); - BUG_ON(!host->claimed); - BUG_ON(!host->bus_ops); + WARN_ON(!host->claimed); + WARN_ON(!host->bus_ops); spin_lock_irqsave(&host->lock, flags); @@ -584,7 +584,7 @@ void mmc_detect_change(struct mmc_host *host, unsigned long delay) #ifdef CONFIG_MMC_DEBUG unsigned long flags; spin_lock_irqsave(&host->lock, flags); - BUG_ON(host->removed); + WARN_ON(host->removed); spin_unlock_irqrestore(&host->lock, flags); #endif diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index ba7a847cfd0..6ba458951d2 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -264,7 +264,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, unsigned int max_dtr; BUG_ON(!host); - BUG_ON(!host->claimed); + WARN_ON(!host->claimed); /* * Since we're changing the OCR value, we seem to @@ -558,7 +558,7 @@ int mmc_attach_mmc(struct mmc_host *host, u32 ocr) int err; BUG_ON(!host); - BUG_ON(!host->claimed); + WARN_ON(!host->claimed); mmc_attach_bus(host, &mmc_ops); diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 0a04a6e86ca..c86588fdaae 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -166,8 +166,6 @@ static int mmc_decode_scr(struct mmc_card *card) unsigned int scr_struct; u32 resp[4]; - BUG_ON(!mmc_card_sd(card)); - resp[3] = card->raw_scr[1]; resp[2] = card->raw_scr[0]; @@ -300,7 +298,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, unsigned int max_dtr; BUG_ON(!host); - BUG_ON(!host->claimed); + WARN_ON(!host->claimed); /* * Since we're changing the OCR value, we seem to @@ -620,7 +618,7 @@ int mmc_attach_sd(struct mmc_host *host, u32 ocr) int err; BUG_ON(!host); - BUG_ON(!host->claimed); + WARN_ON(!host->claimed); mmc_attach_bus(host, &mmc_sd_ops); diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 58cf36e4467..2f3fb994c38 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -230,7 +230,7 @@ int mmc_attach_sdio(struct mmc_host *host, u32 ocr) struct mmc_card *card; BUG_ON(!host); - BUG_ON(!host->claimed); + WARN_ON(!host->claimed); mmc_attach_bus(host, &mmc_sdio_ops); diff --git a/drivers/mmc/core/sdio_irq.c b/drivers/mmc/core/sdio_irq.c index 01daee934d1..8843a4c2fe9 100644 --- a/drivers/mmc/core/sdio_irq.c +++ b/drivers/mmc/core/sdio_irq.c @@ -125,7 +125,7 @@ static int sdio_card_irq_get(struct mmc_card *card) { struct mmc_host *host = card->host; - BUG_ON(!host->claimed); + WARN_ON(!host->claimed); if (!host->sdio_irqs++) { atomic_set(&host->sdio_irq_thread_abort, 0); @@ -145,7 +145,7 @@ static int sdio_card_irq_put(struct mmc_card *card) { struct mmc_host *host = card->host; - BUG_ON(!host->claimed); + WARN_ON(!host->claimed); BUG_ON(host->sdio_irqs < 1); if (!--host->sdio_irqs) { -- cgit v1.2.3 From 7806cdb40fd562e5dcc07321579b62a5dc7cd95c Mon Sep 17 00:00:00 2001 From: David Vrabel Date: Fri, 10 Aug 2007 13:29:46 +0100 Subject: sdio: add sdio_f0_readb() and sdio_f0_writeb() Add sdio_f0_readb() and sdio_f0_writeb() functions to reading and writing function 0 registers. Writes outside the vendor specific CCCR registers (0xF0 - 0xFF) are not permitted. Signed-off-by: David Vrabel Signed-off-by: Pierre Ossman --- drivers/mmc/core/sdio_io.c | 64 +++++++++++++++++++++++++++++++++++++++++++ include/linux/mmc/sdio_func.h | 5 ++++ 2 files changed, 69 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/core/sdio_io.c b/drivers/mmc/core/sdio_io.c index 34b085d4024..625b92ce9ce 100644 --- a/drivers/mmc/core/sdio_io.c +++ b/drivers/mmc/core/sdio_io.c @@ -482,3 +482,67 @@ void sdio_writel(struct sdio_func *func, unsigned long b, unsigned int addr, } EXPORT_SYMBOL_GPL(sdio_writel); +/** + * sdio_f0_readb - read a single byte from SDIO function 0 + * @func: an SDIO function of the card + * @addr: address to read + * @err_ret: optional status value from transfer + * + * Reads a single byte from the address space of SDIO function 0. + * If there is a problem reading the address, 0xff is returned + * and @err_ret will contain the error code. + */ +unsigned char sdio_f0_readb(struct sdio_func *func, unsigned int addr, + int *err_ret) +{ + int ret; + unsigned char val; + + BUG_ON(!func); + + if (err_ret) + *err_ret = 0; + + ret = mmc_io_rw_direct(func->card, 0, 0, addr, 0, &val); + if (ret) { + if (err_ret) + *err_ret = ret; + return 0xFF; + } + + return val; +} +EXPORT_SYMBOL_GPL(sdio_f0_readb); + +/** + * sdio_f0_writeb - write a single byte to SDIO function 0 + * @func: an SDIO function of the card + * @b: byte to write + * @addr: address to write to + * @err_ret: optional status value from transfer + * + * Writes a single byte to the address space of SDIO function 0. + * @err_ret will contain the status of the actual transfer. + * + * Only writes to the vendor specific CCCR registers (0xF0 - + * 0xFF) are permiited; @err_ret will be set to -EINVAL for * + * writes outside this range. + */ +void sdio_f0_writeb(struct sdio_func *func, unsigned char b, unsigned int addr, + int *err_ret) +{ + int ret; + + BUG_ON(!func); + + if (addr < 0xF0 || addr > 0xFF) { + if (err_ret) + *err_ret = -EINVAL; + return; + } + + ret = mmc_io_rw_direct(func->card, 1, 0, addr, b, NULL); + if (err_ret) + *err_ret = ret; +} +EXPORT_SYMBOL_GPL(sdio_f0_writeb); diff --git a/include/linux/mmc/sdio_func.h b/include/linux/mmc/sdio_func.h index f05757984e8..da6a96c3977 100644 --- a/include/linux/mmc/sdio_func.h +++ b/include/linux/mmc/sdio_func.h @@ -141,5 +141,10 @@ extern int sdio_memcpy_toio(struct sdio_func *func, unsigned int addr, extern int sdio_writesb(struct sdio_func *func, unsigned int addr, void *src, int count); +extern unsigned char sdio_f0_readb(struct sdio_func *func, + unsigned int addr, int *err_ret); +extern void sdio_f0_writeb(struct sdio_func *func, unsigned char b, + unsigned int addr, int *err_ret); + #endif -- cgit v1.2.3 From 2ba30eedec37e2f65babf4ea54233f98afbe0871 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 15 Aug 2007 13:27:29 -0400 Subject: sdio: add default c_ispeed/c_ospeed values to sdio_uart driver Note that the default baudrate is 4800 instead of 9600 as a convenience because that's what GPS devices want which is still the main use for this driver. Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman --- drivers/mmc/card/sdio_uart.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/card/sdio_uart.c b/drivers/mmc/card/sdio_uart.c index 190120d1449..606100212af 100644 --- a/drivers/mmc/card/sdio_uart.c +++ b/drivers/mmc/card/sdio_uart.c @@ -1106,6 +1106,8 @@ static int __init sdio_uart_init(void) tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; tty_drv->init_termios = tty_std_termios; tty_drv->init_termios.c_cflag = B4800 | CS8 | CREAD | HUPCL | CLOCAL; + tty_drv->init_termios.c_ispeed = 4800; + tty_drv->init_termios.c_ospeed = 4800; tty_set_operations(tty_drv, &sdio_uart_ops); ret = tty_register_driver(tty_drv); -- cgit v1.2.3 From 15b82b46de358a574c2a6a6dea4c8076bef7ac43 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Mon, 20 Aug 2007 17:17:37 -0400 Subject: sdio: fix recursion issues between sdio-uart driver and tty layer In a few places, sdio_uart_irq() is called directly instead of waiting for the actual interrupt to be raised and the SDIO IRQ thread scheduled in order to reduce latency. However, some interaction with the tty core may end up calling us back (serial echo, flow control, etc.) creating two issues: - the host lock gets claimed twice from the same thread causing a deadlock; - the same direct calls to sdio_uart_irq() may be performed causing unexpected reentrancy into the IRQ handler. This patch handles both of those issues. Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman --- drivers/mmc/card/sdio_uart.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/card/sdio_uart.c b/drivers/mmc/card/sdio_uart.c index 606100212af..d552de68311 100644 --- a/drivers/mmc/card/sdio_uart.c +++ b/drivers/mmc/card/sdio_uart.c @@ -79,6 +79,7 @@ struct sdio_uart_port { struct mutex open_lock; struct sdio_func *func; struct mutex func_lock; + struct task_struct *in_sdio_uart_irq; unsigned int regs_offset; struct circ_buf xmit; spinlock_t write_lock; @@ -186,14 +187,16 @@ static int sdio_uart_claim_func(struct sdio_uart_port *port) mutex_unlock(&port->func_lock); return -ENODEV; } - sdio_claim_host(port->func); + if (likely(port->in_sdio_uart_irq != current)) + sdio_claim_host(port->func); mutex_unlock(&port->func_lock); return 0; } static inline void sdio_uart_release_func(struct sdio_uart_port *port) { - sdio_release_host(port->func); + if (likely(port->in_sdio_uart_irq != current)) + sdio_release_host(port->func); } static inline unsigned int sdio_in(struct sdio_uart_port *port, int offset) @@ -511,15 +514,29 @@ static void sdio_uart_irq(struct sdio_func *func) struct sdio_uart_port *port = sdio_get_drvdata(func); unsigned int iir, lsr; + /* + * In a few places sdio_uart_irq() is called directly instead of + * waiting for the actual interrupt to be raised and the SDIO IRQ + * thread scheduled in order to reduce latency. However, some + * interaction with the tty core may end up calling us back + * (serial echo, flow control, etc.) through those same places + * causing undesirable effects. Let's stop the recursion here. + */ + if (unlikely(port->in_sdio_uart_irq == current)) + return; + iir = sdio_in(port, UART_IIR); if (iir & UART_IIR_NO_INT) return; + + port->in_sdio_uart_irq = current; lsr = sdio_in(port, UART_LSR); if (lsr & UART_LSR_DR) sdio_uart_receive_chars(port, &lsr); sdio_uart_check_modem_status(port); if (lsr & UART_LSR_THRE) sdio_uart_transmit_chars(port); + port->in_sdio_uart_irq = NULL; } static int sdio_uart_startup(struct sdio_uart_port *port) -- cgit v1.2.3 From f75979b77fb20b01522d8fab96dfc76cc9f42420 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 4 Sep 2007 07:59:18 +0200 Subject: sdhci: sdio interrupt support Add support for relaying the sdio interrupt signal from the card. Signed-off-by: Pierre Ossman --- drivers/mmc/host/sdhci.c | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index c63edc5c17e..228ce42371d 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -800,10 +800,35 @@ static int sdhci_get_ro(struct mmc_host *mmc) return !(present & SDHCI_WRITE_PROTECT); } +static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + struct sdhci_host *host; + unsigned long flags; + u32 ier; + + host = mmc_priv(mmc); + + spin_lock_irqsave(&host->lock, flags); + + ier = readl(host->ioaddr + SDHCI_INT_ENABLE); + + ier &= ~SDHCI_INT_CARD_INT; + if (enable) + ier |= SDHCI_INT_CARD_INT; + + writel(ier, host->ioaddr + SDHCI_INT_ENABLE); + writel(ier, host->ioaddr + SDHCI_SIGNAL_ENABLE); + + mmiowb(); + + spin_unlock_irqrestore(&host->lock, flags); +} + static const struct mmc_host_ops sdhci_ops = { .request = sdhci_request, .set_ios = sdhci_set_ios, .get_ro = sdhci_get_ro, + .enable_sdio_irq = sdhci_enable_sdio_irq, }; /*****************************************************************************\ @@ -1012,6 +1037,7 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) irqreturn_t result; struct sdhci_host* host = dev_id; u32 intmask; + int cardint = 0; spin_lock(&host->lock); @@ -1056,6 +1082,11 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) intmask &= ~SDHCI_INT_BUS_POWER; + if (intmask & SDHCI_INT_CARD_INT) + cardint = 1; + + intmask &= ~SDHCI_INT_CARD_INT; + if (intmask) { printk(KERN_ERR "%s: Unexpected interrupt 0x%08x.\n", mmc_hostname(host->mmc), intmask); @@ -1070,6 +1101,12 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) out: spin_unlock(&host->lock); + /* + * We have to delay this as it calls back into the driver. + */ + if (cardint) + mmc_signal_sdio_irq(host->mmc); + return result; } @@ -1309,7 +1346,7 @@ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot) mmc->ops = &sdhci_ops; mmc->f_min = host->max_clk / 256; mmc->f_max = host->max_clk; - mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_MULTIWRITE; + mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_MULTIWRITE | MMC_CAP_SDIO_IRQ; if (caps & SDHCI_CAN_DO_HISPD) mmc->caps |= MMC_CAP_SD_HIGHSPEED; -- cgit v1.2.3 From f9996aee36921e8f1d499de1b2ea380855cf6d97 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Wed, 19 Sep 2007 18:38:50 +0200 Subject: mmc: increase power up delay Increase delay for power up in order to support some slower boards. Also add some comments about why the delays are there. Signed-off-by: Pierre Ossman --- drivers/mmc/core/core.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 3bebd3b55dc..bffcaf8df35 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -460,12 +460,20 @@ static void mmc_power_up(struct mmc_host *host) host->ios.timing = MMC_TIMING_LEGACY; mmc_set_ios(host); - mmc_delay(1); + /* + * This delay should be sufficient to allow the power supply + * to reach the minimum voltage. + */ + mmc_delay(2); host->ios.clock = host->f_min; host->ios.power_mode = MMC_POWER_ON; mmc_set_ios(host); + /* + * This delay must be at least 74 clock sizes, or 1 ms, or the + * time required to reach a stable voltage. + */ mmc_delay(2); } -- cgit v1.2.3 From 759bdc7af450404382e937c76722ae8736daef92 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Wed, 19 Sep 2007 18:42:16 +0200 Subject: sdio: store vendor strings Store vendor strings found in CISTPL_VERS_1 so that function drivers can access them. Signed-off-by: Pierre Ossman --- drivers/mmc/core/bus.c | 3 +++ drivers/mmc/core/sdio_bus.c | 3 +++ drivers/mmc/core/sdio_cis.c | 50 ++++++++++++++++++++++++++++++++++++++++++- include/linux/mmc/card.h | 2 ++ include/linux/mmc/sdio_func.h | 3 +++ 5 files changed, 60 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index 1cc11714916..733ac95331c 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -187,6 +187,9 @@ static void mmc_release_card(struct device *dev) sdio_free_common_cis(card); + if (card->info) + kfree(card->info); + kfree(card); } diff --git a/drivers/mmc/core/sdio_bus.c b/drivers/mmc/core/sdio_bus.c index 683d9174010..0713a8c71e5 100644 --- a/drivers/mmc/core/sdio_bus.c +++ b/drivers/mmc/core/sdio_bus.c @@ -211,6 +211,9 @@ static void sdio_release_func(struct device *dev) sdio_free_func_cis(func); + if (func->info) + kfree(func->info); + kfree(func); } diff --git a/drivers/mmc/core/sdio_cis.c b/drivers/mmc/core/sdio_cis.c index 1d03f12bbb3..d5e51b1c7b3 100644 --- a/drivers/mmc/core/sdio_cis.c +++ b/drivers/mmc/core/sdio_cis.c @@ -23,6 +23,54 @@ #include "sdio_cis.h" #include "sdio_ops.h" +static int cistpl_vers_1(struct mmc_card *card, struct sdio_func *func, + const unsigned char *buf, unsigned size) +{ + unsigned i, nr_strings; + char **buffer, *string; + + buf += 2; + size -= 2; + + nr_strings = 0; + for (i = 0; i < size; i++) { + if (buf[i] == 0xff) + break; + if (buf[i] == 0) + nr_strings++; + } + + if (buf[i-1] != '\0') { + printk(KERN_WARNING "SDIO: ignoring broken CISTPL_VERS_1\n"); + return 0; + } + + size = i; + + buffer = kzalloc(sizeof(char*) * nr_strings + size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + string = (char*)(buffer + nr_strings); + + for (i = 0; i < nr_strings; i++) { + buffer[i] = string; + strcpy(string, buf); + string += strlen(string) + 1; + buf += strlen(buf) + 1; + } + + if (func) { + func->num_info = nr_strings; + func->info = (const char**)buffer; + } else { + card->num_info = nr_strings; + card->info = (const char**)buffer; + } + + return 0; +} + static int cistpl_manfid(struct mmc_card *card, struct sdio_func *func, const unsigned char *buf, unsigned size) { @@ -119,7 +167,7 @@ struct cis_tpl { }; static const struct cis_tpl cis_tpl_list[] = { - { 0x15, 3, /* cistpl_vers_1 */ }, + { 0x15, 3, cistpl_vers_1 }, { 0x20, 4, cistpl_manfid }, { 0x21, 2, /* cistpl_funcid */ }, { 0x22, 0, cistpl_funce }, diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index a444431e28b..0d508ac17d6 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -108,6 +108,8 @@ struct mmc_card { struct sdio_cccr cccr; /* common card info */ struct sdio_cis cis; /* common tuple info */ struct sdio_func *sdio_func[SDIO_MAX_FUNCS]; /* SDIO functions (devices) */ + unsigned num_info; /* number of info strings */ + const char **info; /* info strings */ struct sdio_func_tuple *tuples; /* unknown common tuples */ }; diff --git a/include/linux/mmc/sdio_func.h b/include/linux/mmc/sdio_func.h index da6a96c3977..b050f4d7b41 100644 --- a/include/linux/mmc/sdio_func.h +++ b/include/linux/mmc/sdio_func.h @@ -51,6 +51,9 @@ struct sdio_func { u8 tmpbuf[4]; /* DMA:able scratch buffer */ + unsigned num_info; /* number of info strings */ + const char **info; /* info strings */ + struct sdio_func_tuple *tuples; }; -- cgit v1.2.3 From 7213d175e3b6f6db60f843b72e88857a350e146a Mon Sep 17 00:00:00 2001 From: David Brownell Date: Wed, 8 Aug 2007 09:10:23 -0700 Subject: MMC/SD card driver learns SPI Teaching the MMC/SD block card driver about SPI. - Provide the SPI response type flags with each request issued. - Understand that multiblock SPI writes don't use STOP_TRANSMISSION. - Correct check for APP_CMD failure. Signed-off-by: David Brownell Signed-off-by: Pierre Ossman --- drivers/mmc/card/block.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index ab510689ecd..e38d5a3b2a8 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -151,17 +151,19 @@ static u32 mmc_sd_num_wr_blocks(struct mmc_card *card) cmd.opcode = MMC_APP_CMD; cmd.arg = card->rca << 16; - cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC; err = mmc_wait_for_cmd(card->host, &cmd, 0); - if (err || !(cmd.resp[0] & R1_APP_CMD)) + if (err) + return (u32)-1; + if (!mmc_host_is_spi(card->host) && !(cmd.resp[0] & R1_APP_CMD)) return (u32)-1; memset(&cmd, 0, sizeof(struct mmc_command)); cmd.opcode = SD_APP_SEND_NUM_WR_BLKS; cmd.arg = 0; - cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; memset(&data, 0, sizeof(struct mmc_data)); @@ -220,11 +222,11 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) brq.cmd.arg = req->sector; if (!mmc_card_blockaddr(card)) brq.cmd.arg <<= 9; - brq.cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + brq.cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; brq.data.blksz = 1 << md->block_bits; brq.stop.opcode = MMC_STOP_TRANSMISSION; brq.stop.arg = 0; - brq.stop.flags = MMC_RSP_R1B | MMC_CMD_AC; + brq.stop.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC; brq.data.blocks = req->nr_sectors >> (md->block_bits - 9); if (brq.data.blocks > card->host->max_blk_count) brq.data.blocks = card->host->max_blk_count; @@ -241,7 +243,12 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) brq.data.blocks = 1; if (brq.data.blocks > 1) { - brq.mrq.stop = &brq.stop; + /* SPI multiblock writes terminate using a special + * token, not a STOP_TRANSMISSION request. + */ + if (!mmc_host_is_spi(card->host) + || rq_data_dir(req) == READ) + brq.mrq.stop = &brq.stop; readcmd = MMC_READ_MULTIPLE_BLOCK; writecmd = MMC_WRITE_MULTIPLE_BLOCK; } else { @@ -301,7 +308,7 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) goto cmd_err; } - if (rq_data_dir(req) != READ) { + if (!mmc_host_is_spi(card->host) && rq_data_dir(req) != READ) { do { int err; @@ -509,7 +516,7 @@ mmc_blk_set_blksize(struct mmc_blk_data *md, struct mmc_card *card) mmc_claim_host(card->host); cmd.opcode = MMC_SET_BLOCKLEN; cmd.arg = 1 << md->block_bits; - cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC; err = mmc_wait_for_cmd(card->host, &cmd, 5); mmc_release_host(card->host); -- cgit v1.2.3 From af51715079e7fb6b290e1881d63d815dc4de5011 Mon Sep 17 00:00:00 2001 From: David Brownell Date: Wed, 8 Aug 2007 09:11:32 -0700 Subject: MMC core learns about SPI Teach the MMC/SD/SDIO core about using SPI mode. - Use mmc_host_is_spi() so enumeration works through SPI signaling and protocols, not just the native versions. - Provide the SPI response type flags with each request issued, including requests from the new lock/unlock code. - Understand that cmd->resp[0] and mmc_get_status() results for SPI return different values than for "native" MMC/SD protocol; this affects resetting, checking card lock status, and some others. - Understand that some commands act a bit differently ... notably: * OP_COND command doesn't return the OCR * APP_CMD status doesn't have an R1_APP_CMD analogue Those changes required some new and updated primitives: - Provide utilities to access two SPI-only requests, and one request that wasn't previously needed: * mmc_spi_read_ocr() ... SPI only * mmc_spi_set_crc() ... SPI only (override by module parm) * mmc_send_cid() ... for use without broadcast mode - Updated internal routines: * Previous mmc_send_csd() modified into mmc_send_cxd_native(); it uses native "R2" responses, which include 16 bytes of data. * Previous mmc_send_ext_csd() becomes new mmc_send_cxd_data() helper for command-and-data access * Bugfix to that mmc_send_cxd_data() code: dma-to-stack is unsafe/nonportable, so kmalloc a bounce buffer instead. - Modified mmc_send_ext_csd() now uses mmc_send_cxd_data() helper - Modified mmc_send_csd(), and new mmc_spi_send_cid(), routines use those helper routines based on whether they're native or SPI The newest categories of cards supported by the MMC stack aren't expected to work yet with SPI: MMC or SD cards with over 4GB data, and SDIO. All those cards support SPI mode, so eventually they should work too. Signed-off-by: David Brownell Signed-off-by: Pierre Ossman --- drivers/mmc/core/bus.c | 24 +++++-- drivers/mmc/core/core.c | 28 ++++++-- drivers/mmc/core/core.h | 2 + drivers/mmc/core/mmc.c | 50 +++++++++---- drivers/mmc/core/mmc_ops.c | 166 +++++++++++++++++++++++++++++++++++--------- drivers/mmc/core/mmc_ops.h | 3 + drivers/mmc/core/sd.c | 48 ++++++++++--- drivers/mmc/core/sd_ops.c | 59 ++++++++++++---- drivers/mmc/core/sdio.c | 29 +++++--- drivers/mmc/core/sdio_ops.c | 66 +++++++++++++----- 10 files changed, 372 insertions(+), 103 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index 733ac95331c..8d6f6014870 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -243,10 +243,17 @@ int mmc_add_card(struct mmc_card *card) break; } - printk(KERN_INFO "%s: new %s%s card at address %04x\n", - mmc_hostname(card->host), - mmc_card_highspeed(card) ? "high speed " : "", - type, card->rca); + if (mmc_host_is_spi(card->host)) { + printk(KERN_INFO "%s: new %s%s card on SPI\n", + mmc_hostname(card->host), + mmc_card_highspeed(card) ? "high speed " : "", + type); + } else { + printk(KERN_INFO "%s: new %s%s card at address %04x\n", + mmc_hostname(card->host), + mmc_card_highspeed(card) ? "high speed " : "", + type, card->rca); + } card->dev.uevent_suppress = 1; @@ -278,8 +285,13 @@ int mmc_add_card(struct mmc_card *card) void mmc_remove_card(struct mmc_card *card) { if (mmc_card_present(card)) { - printk(KERN_INFO "%s: card %04x removed\n", - mmc_hostname(card->host), card->rca); + if (mmc_host_is_spi(card->host)) { + printk(KERN_INFO "%s: SPI card removed\n", + mmc_hostname(card->host)); + } else { + printk(KERN_INFO "%s: card %04x removed\n", + mmc_hostname(card->host), card->rca); + } if (card->host->bus_ops->sysfs_remove) card->host->bus_ops->sysfs_remove(card->host, card); diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index bffcaf8df35..bad39442f8f 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -41,6 +41,14 @@ extern int mmc_attach_sdio(struct mmc_host *host, u32 ocr); static struct workqueue_struct *workqueue; +/* + * Enabling software CRCs on the data blocks can be a significant (30%) + * performance cost, and for other reasons may not always be desired. + * So we allow it it to be disabled. + */ +int use_spi_crc = 1; +module_param(use_spi_crc, bool, 0); + /* * Internal function. Schedule delayed work in the MMC work queue. */ @@ -71,6 +79,11 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) struct mmc_command *cmd = mrq->cmd; int err = cmd->error; + if (err && cmd->retries && mmc_host_is_spi(host)) { + if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND) + cmd->retries = 0; + } + if (err && cmd->retries) { pr_debug("%s: req failed (CMD%u): %d, retrying...\n", mmc_hostname(host), cmd->opcode, err); @@ -453,8 +466,13 @@ static void mmc_power_up(struct mmc_host *host) int bit = fls(host->ocr_avail) - 1; host->ios.vdd = bit; - host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN; - host->ios.chip_select = MMC_CS_DONTCARE; + if (mmc_host_is_spi(host)) { + host->ios.chip_select = MMC_CS_HIGH; + host->ios.bus_mode = MMC_BUSMODE_PUSHPULL; + } else { + host->ios.chip_select = MMC_CS_DONTCARE; + host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN; + } host->ios.power_mode = MMC_POWER_UP; host->ios.bus_width = MMC_BUS_WIDTH_1; host->ios.timing = MMC_TIMING_LEGACY; @@ -481,8 +499,10 @@ static void mmc_power_off(struct mmc_host *host) { host->ios.clock = 0; host->ios.vdd = 0; - host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN; - host->ios.chip_select = MMC_CS_DONTCARE; + if (!mmc_host_is_spi(host)) { + host->ios.bus_mode = MMC_BUSMODE_OPENDRAIN; + host->ios.chip_select = MMC_CS_DONTCARE; + } host->ios.power_mode = MMC_POWER_OFF; host->ios.bus_width = MMC_BUS_WIDTH_1; host->ios.timing = MMC_TIMING_LEGACY; diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h index bb2774af9ea..39daf2fb5dc 100644 --- a/drivers/mmc/core/core.h +++ b/drivers/mmc/core/core.h @@ -48,5 +48,7 @@ void mmc_rescan(struct work_struct *work); void mmc_start_host(struct mmc_host *host); void mmc_stop_host(struct mmc_host *host); +extern int use_spi_crc; + #endif diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 6ba458951d2..65fe28860f5 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -165,8 +165,6 @@ static int mmc_read_ext_csd(struct mmc_card *card) BUG_ON(!card); - err = -EIO; - if (card->csd.mmca_vsn < CSD_SPEC_VER_4) return 0; @@ -279,10 +277,22 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, if (err) goto err; + /* + * For SPI, enable CRC as appropriate. + */ + if (mmc_host_is_spi(host)) { + err = mmc_spi_set_crc(host, use_spi_crc); + if (err) + goto err; + } + /* * Fetch CID from card. */ - err = mmc_all_send_cid(host, cid); + if (mmc_host_is_spi(host)) + err = mmc_send_cid(host, cid); + else + err = mmc_all_send_cid(host, cid); if (err) goto err; @@ -309,13 +319,15 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, } /* - * Set card RCA. + * For native busses: set card RCA and quit open drain mode. */ - err = mmc_set_relative_addr(card); - if (err) - goto free_card; + if (!mmc_host_is_spi(host)) { + err = mmc_set_relative_addr(card); + if (err) + goto free_card; - mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); + mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); + } if (!oldcard) { /* @@ -336,13 +348,15 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, /* * Select card, as all following commands rely on that. */ - err = mmc_select_card(card); - if (err) - goto free_card; + if (!mmc_host_is_spi(host)) { + err = mmc_select_card(card); + if (err) + goto free_card; + } if (!oldcard) { /* - * Fetch and process extened CSD. + * Fetch and process extended CSD. */ err = mmc_read_ext_csd(card); if (err) @@ -502,7 +516,8 @@ static void mmc_suspend(struct mmc_host *host) BUG_ON(!host->card); mmc_claim_host(host); - mmc_deselect_cards(host); + if (!mmc_host_is_spi(host)) + mmc_deselect_cards(host); host->card->state &= ~MMC_STATE_HIGHSPEED; mmc_release_host(host); } @@ -562,6 +577,15 @@ int mmc_attach_mmc(struct mmc_host *host, u32 ocr) mmc_attach_bus(host, &mmc_ops); + /* + * We need to get OCR a different way for SPI. + */ + if (mmc_host_is_spi(host)) { + err = mmc_spi_read_ocr(host, 1, &ocr); + if (err) + goto err; + } + /* * Sanity check the voltages that the card claims to * support. diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index 39567f91a4b..bf4bc6adcfe 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -63,23 +63,36 @@ int mmc_go_idle(struct mmc_host *host) int err; struct mmc_command cmd; - mmc_set_chip_select(host, MMC_CS_HIGH); - - mmc_delay(1); + /* + * Non-SPI hosts need to prevent chipselect going active during + * GO_IDLE; that would put chips into SPI mode. Remind them of + * that in case of hardware that won't pull up DAT3/nCS otherwise. + * + * SPI hosts ignore ios.chip_select; it's managed according to + * rules that must accomodate non-MMC slaves which this layer + * won't even know about. + */ + if (!mmc_host_is_spi(host)) { + mmc_set_chip_select(host, MMC_CS_HIGH); + mmc_delay(1); + } memset(&cmd, 0, sizeof(struct mmc_command)); cmd.opcode = MMC_GO_IDLE_STATE; cmd.arg = 0; - cmd.flags = MMC_RSP_NONE | MMC_CMD_BC; + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_NONE | MMC_CMD_BC; err = mmc_wait_for_cmd(host, &cmd, 0); mmc_delay(1); - mmc_set_chip_select(host, MMC_CS_DONTCARE); + if (!mmc_host_is_spi(host)) { + mmc_set_chip_select(host, MMC_CS_DONTCARE); + mmc_delay(1); + } - mmc_delay(1); + host->use_spi_crc = 0; return err; } @@ -94,23 +107,33 @@ int mmc_send_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr) memset(&cmd, 0, sizeof(struct mmc_command)); cmd.opcode = MMC_SEND_OP_COND; - cmd.arg = ocr; - cmd.flags = MMC_RSP_R3 | MMC_CMD_BCR; + cmd.arg = mmc_host_is_spi(host) ? 0 : ocr; + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R3 | MMC_CMD_BCR; for (i = 100; i; i--) { err = mmc_wait_for_cmd(host, &cmd, 0); if (err) break; - if (cmd.resp[0] & MMC_CARD_BUSY || ocr == 0) + /* if we're just probing, do a single pass */ + if (ocr == 0) break; + /* otherwise wait until reset completes */ + if (mmc_host_is_spi(host)) { + if (!(cmd.resp[0] & R1_SPI_IDLE)) + break; + } else { + if (cmd.resp[0] & MMC_CARD_BUSY) + break; + } + err = -ETIMEDOUT; mmc_delay(10); } - if (rocr) + if (rocr && !mmc_host_is_spi(host)) *rocr = cmd.resp[0]; return err; @@ -160,40 +183,46 @@ int mmc_set_relative_addr(struct mmc_card *card) return 0; } -int mmc_send_csd(struct mmc_card *card, u32 *csd) +static int +mmc_send_cxd_native(struct mmc_host *host, u32 arg, u32 *cxd, int opcode) { int err; struct mmc_command cmd; - BUG_ON(!card); - BUG_ON(!card->host); - BUG_ON(!csd); + BUG_ON(!host); + BUG_ON(!cxd); memset(&cmd, 0, sizeof(struct mmc_command)); - cmd.opcode = MMC_SEND_CSD; - cmd.arg = card->rca << 16; + cmd.opcode = opcode; + cmd.arg = arg; cmd.flags = MMC_RSP_R2 | MMC_CMD_AC; - err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES); + err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES); if (err) return err; - memcpy(csd, cmd.resp, sizeof(u32) * 4); + memcpy(cxd, cmd.resp, sizeof(u32) * 4); return 0; } -int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd) +static int +mmc_send_cxd_data(struct mmc_card *card, struct mmc_host *host, + u32 opcode, void *buf, unsigned len) { struct mmc_request mrq; struct mmc_command cmd; struct mmc_data data; struct scatterlist sg; + void *data_buf; - BUG_ON(!card); - BUG_ON(!card->host); - BUG_ON(!ext_csd); + /* dma onto stack is unsafe/nonportable, but callers to this + * routine normally provide temporary on-stack buffers ... + */ + data_buf = kmalloc(len, GFP_KERNEL); + if (data_buf == NULL) + return -ENOMEM; memset(&mrq, 0, sizeof(struct mmc_request)); memset(&cmd, 0, sizeof(struct mmc_command)); @@ -202,21 +231,31 @@ int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd) mrq.cmd = &cmd; mrq.data = &data; - cmd.opcode = MMC_SEND_EXT_CSD; + cmd.opcode = opcode; cmd.arg = 0; - cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; - data.blksz = 512; + /* NOTE HACK: the MMC_RSP_SPI_R1 is always correct here, but we + * rely on callers to never use this with "native" calls for reading + * CSD or CID. Native versions of those commands use the R2 type, + * not R1 plus a data block. + */ + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; + + data.blksz = len; data.blocks = 1; data.flags = MMC_DATA_READ; data.sg = &sg; data.sg_len = 1; - sg_init_one(&sg, ext_csd, 512); + sg_init_one(&sg, data_buf, len); - mmc_set_data_timeout(&data, card); + if (card) + mmc_set_data_timeout(&data, card); - mmc_wait_for_req(card->host, &mrq); + mmc_wait_for_req(host, &mrq); + + memcpy(buf, data_buf, len); + kfree(data_buf); if (cmd.error) return cmd.error; @@ -226,6 +265,67 @@ int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd) return 0; } +int mmc_send_csd(struct mmc_card *card, u32 *csd) +{ + if (!mmc_host_is_spi(card->host)) + return mmc_send_cxd_native(card->host, card->rca << 16, + csd, MMC_SEND_CSD); + + return mmc_send_cxd_data(card, card->host, MMC_SEND_CSD, csd, 16); +} + +int mmc_send_cid(struct mmc_host *host, u32 *cid) +{ + if (!mmc_host_is_spi(host)) { + if (!host->card) + return -EINVAL; + return mmc_send_cxd_native(host, host->card->rca << 16, + cid, MMC_SEND_CID); + } + + return mmc_send_cxd_data(NULL, host, MMC_SEND_CID, cid, 16); +} + +int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd) +{ + return mmc_send_cxd_data(card, card->host, MMC_SEND_EXT_CSD, + ext_csd, 512); +} + +int mmc_spi_read_ocr(struct mmc_host *host, int highcap, u32 *ocrp) +{ + struct mmc_command cmd; + int err; + + memset(&cmd, 0, sizeof(struct mmc_command)); + + cmd.opcode = MMC_SPI_READ_OCR; + cmd.arg = highcap ? (1 << 30) : 0; + cmd.flags = MMC_RSP_SPI_R3; + + err = mmc_wait_for_cmd(host, &cmd, 0); + + *ocrp = cmd.resp[1]; + return err; +} + +int mmc_spi_set_crc(struct mmc_host *host, int use_crc) +{ + struct mmc_command cmd; + int err; + + memset(&cmd, 0, sizeof(struct mmc_command)); + + cmd.opcode = MMC_SPI_CRC_ON_OFF; + cmd.flags = MMC_RSP_SPI_R1; + cmd.arg = use_crc; + + err = mmc_wait_for_cmd(host, &cmd, 0); + if (!err) + host->use_spi_crc = use_crc; + return err; +} + int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value) { int err; @@ -241,7 +341,7 @@ int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value) (index << 16) | (value << 8) | set; - cmd.flags = MMC_RSP_R1B | MMC_CMD_AC; + cmd.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC; err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES); if (err) @@ -261,13 +361,17 @@ int mmc_send_status(struct mmc_card *card, u32 *status) memset(&cmd, 0, sizeof(struct mmc_command)); cmd.opcode = MMC_SEND_STATUS; - cmd.arg = card->rca << 16; - cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + if (!mmc_host_is_spi(card->host)) + cmd.arg = card->rca << 16; + cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC; err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES); if (err) return err; + /* NOTE: callers are required to understand the difference + * between "native" and SPI format status words! + */ if (status) *status = cmd.resp[0]; diff --git a/drivers/mmc/core/mmc_ops.h b/drivers/mmc/core/mmc_ops.h index 76d09a93c5d..17854bf7cf0 100644 --- a/drivers/mmc/core/mmc_ops.h +++ b/drivers/mmc/core/mmc_ops.h @@ -22,6 +22,9 @@ int mmc_send_csd(struct mmc_card *card, u32 *csd); int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd); int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value); int mmc_send_status(struct mmc_card *card, u32 *status); +int mmc_send_cid(struct mmc_host *host, u32 *cid); +int mmc_spi_read_ocr(struct mmc_host *host, int highcap, u32 *ocrp); +int mmc_spi_set_crc(struct mmc_host *host, int use_crc); #endif diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index c86588fdaae..d1c1e0f592f 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -322,10 +322,22 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, if (err) goto err; + /* + * For SPI, enable CRC as appropriate. + */ + if (mmc_host_is_spi(host)) { + err = mmc_spi_set_crc(host, use_spi_crc); + if (err) + goto err; + } + /* * Fetch CID from card. */ - err = mmc_all_send_cid(host, cid); + if (mmc_host_is_spi(host)) + err = mmc_send_cid(host, cid); + else + err = mmc_all_send_cid(host, cid); if (err) goto err; @@ -351,13 +363,15 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, } /* - * Set card RCA. + * For native busses: get card RCA and quit open drain mode. */ - err = mmc_send_relative_addr(host, &card->rca); - if (err) - goto free_card; + if (!mmc_host_is_spi(host)) { + err = mmc_send_relative_addr(host, &card->rca); + if (err) + goto free_card; - mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); + mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); + } if (!oldcard) { /* @@ -377,9 +391,11 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, /* * Select card, as all following commands rely on that. */ - err = mmc_select_card(card); - if (err) - goto free_card; + if (!mmc_host_is_spi(host)) { + err = mmc_select_card(card); + if (err) + goto free_card; + } if (!oldcard) { /* @@ -562,7 +578,8 @@ static void mmc_sd_suspend(struct mmc_host *host) BUG_ON(!host->card); mmc_claim_host(host); - mmc_deselect_cards(host); + if (!mmc_host_is_spi(host)) + mmc_deselect_cards(host); host->card->state &= ~MMC_STATE_HIGHSPEED; mmc_release_host(host); } @@ -622,6 +639,17 @@ int mmc_attach_sd(struct mmc_host *host, u32 ocr) mmc_attach_bus(host, &mmc_sd_ops); + /* + * We need to get OCR a different way for SPI. + */ + if (mmc_host_is_spi(host)) { + mmc_go_idle(host); + + err = mmc_spi_read_ocr(host, 0, &ocr); + if (err) + goto err; + } + /* * Sanity check the voltages that the card claims to * support. diff --git a/drivers/mmc/core/sd_ops.c b/drivers/mmc/core/sd_ops.c index 491e0306b1b..ee4029a24ef 100644 --- a/drivers/mmc/core/sd_ops.c +++ b/drivers/mmc/core/sd_ops.c @@ -33,10 +33,10 @@ static int mmc_app_cmd(struct mmc_host *host, struct mmc_card *card) if (card) { cmd.arg = card->rca << 16; - cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC; } else { cmd.arg = 0; - cmd.flags = MMC_RSP_R1 | MMC_CMD_BCR; + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_BCR; } err = mmc_wait_for_cmd(host, &cmd, 0); @@ -44,7 +44,7 @@ static int mmc_app_cmd(struct mmc_host *host, struct mmc_card *card) return err; /* Check that card supported application commands */ - if (!(cmd.resp[0] & R1_APP_CMD)) + if (!mmc_host_is_spi(host) && !(cmd.resp[0] & R1_APP_CMD)) return -EOPNOTSUPP; return 0; @@ -83,8 +83,14 @@ int mmc_wait_for_app_cmd(struct mmc_host *host, struct mmc_card *card, memset(&mrq, 0, sizeof(struct mmc_request)); err = mmc_app_cmd(host, card); - if (err) + if (err) { + /* no point in retrying; no APP commands allowed */ + if (mmc_host_is_spi(host)) { + if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND) + break; + } continue; + } memset(&mrq, 0, sizeof(struct mmc_request)); @@ -99,6 +105,12 @@ int mmc_wait_for_app_cmd(struct mmc_host *host, struct mmc_card *card, err = cmd->error; if (!cmd->error) break; + + /* no point in retrying illegal APP commands */ + if (mmc_host_is_spi(host)) { + if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND) + break; + } } return err; @@ -147,23 +159,36 @@ int mmc_send_app_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr) memset(&cmd, 0, sizeof(struct mmc_command)); cmd.opcode = SD_APP_OP_COND; - cmd.arg = ocr; - cmd.flags = MMC_RSP_R3 | MMC_CMD_BCR; + if (mmc_host_is_spi(host)) + cmd.arg = ocr & (1 << 30); /* SPI only defines one bit */ + else + cmd.arg = ocr; + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R3 | MMC_CMD_BCR; for (i = 100; i; i--) { err = mmc_wait_for_app_cmd(host, NULL, &cmd, MMC_CMD_RETRIES); if (err) break; - if (cmd.resp[0] & MMC_CARD_BUSY || ocr == 0) + /* if we're just probing, do a single pass */ + if (ocr == 0) break; + /* otherwise wait until reset completes */ + if (mmc_host_is_spi(host)) { + if (!(cmd.resp[0] & R1_SPI_IDLE)) + break; + } else { + if (cmd.resp[0] & MMC_CARD_BUSY) + break; + } + err = -ETIMEDOUT; mmc_delay(10); } - if (rocr) + if (rocr && !mmc_host_is_spi(host)) *rocr = cmd.resp[0]; return err; @@ -174,6 +199,7 @@ int mmc_send_if_cond(struct mmc_host *host, u32 ocr) struct mmc_command cmd; int err; static const u8 test_pattern = 0xAA; + u8 result_pattern; /* * To support SD 2.0 cards, we must always invoke SD_SEND_IF_COND @@ -182,13 +208,18 @@ int mmc_send_if_cond(struct mmc_host *host, u32 ocr) */ cmd.opcode = SD_SEND_IF_COND; cmd.arg = ((ocr & 0xFF8000) != 0) << 8 | test_pattern; - cmd.flags = MMC_RSP_R7 | MMC_CMD_BCR; + cmd.flags = MMC_RSP_SPI_R7 | MMC_RSP_R7 | MMC_CMD_BCR; err = mmc_wait_for_cmd(host, &cmd, 0); if (err) return err; - if ((cmd.resp[0] & 0xFF) != test_pattern) + if (mmc_host_is_spi(host)) + result_pattern = cmd.resp[1] & 0xFF; + else + result_pattern = cmd.resp[0] & 0xFF; + + if (result_pattern != test_pattern) return -EIO; return 0; @@ -229,6 +260,8 @@ int mmc_app_send_scr(struct mmc_card *card, u32 *scr) BUG_ON(!card->host); BUG_ON(!scr); + /* NOTE: caller guarantees scr is heap-allocated */ + err = mmc_app_cmd(card->host, card); if (err) return err; @@ -242,7 +275,7 @@ int mmc_app_send_scr(struct mmc_card *card, u32 *scr) cmd.opcode = SD_APP_SEND_SCR; cmd.arg = 0; - cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; data.blksz = 8; data.blocks = 1; @@ -278,6 +311,8 @@ int mmc_sd_switch(struct mmc_card *card, int mode, int group, BUG_ON(!card); BUG_ON(!card->host); + /* NOTE: caller guarantees resp is heap-allocated */ + mode = !!mode; value &= 0xF; @@ -292,7 +327,7 @@ int mmc_sd_switch(struct mmc_card *card, int mode, int group, cmd.arg = mode << 31 | 0x00FFFFFF; cmd.arg &= ~(0xF << (group * 4)); cmd.arg |= value << (group * 4); - cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; data.blksz = 64; data.blocks = 1; diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 2f3fb994c38..87a50f456ef 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -269,6 +269,15 @@ int mmc_attach_sdio(struct mmc_host *host, u32 ocr) if (err) goto err; + /* + * For SPI, enable CRC as appropriate. + */ + if (mmc_host_is_spi(host)) { + err = mmc_spi_set_crc(host, use_spi_crc); + if (err) + goto err; + } + /* * The number of functions on the card is encoded inside * the ocr. @@ -290,20 +299,24 @@ int mmc_attach_sdio(struct mmc_host *host, u32 ocr) host->card = card; /* - * Set card RCA. + * For native busses: set card RCA and quit open drain mode. */ - err = mmc_send_relative_addr(host, &card->rca); - if (err) - goto remove; + if (!mmc_host_is_spi(host)) { + err = mmc_send_relative_addr(host, &card->rca); + if (err) + goto remove; - mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); + mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); + } /* * Select card, as all following commands rely on that. */ - err = mmc_select_card(card); - if (err) - goto remove; + if (!mmc_host_is_spi(host)) { + err = mmc_select_card(card); + if (err) + goto remove; + } /* * Read the common registers. diff --git a/drivers/mmc/core/sdio_ops.c b/drivers/mmc/core/sdio_ops.c index 98e20532452..4d289b27503 100644 --- a/drivers/mmc/core/sdio_ops.c +++ b/drivers/mmc/core/sdio_ops.c @@ -30,23 +30,39 @@ int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr) cmd.opcode = SD_IO_SEND_OP_COND; cmd.arg = ocr; - cmd.flags = MMC_RSP_R4 | MMC_CMD_BCR; + cmd.flags = MMC_RSP_SPI_R4 | MMC_RSP_R4 | MMC_CMD_BCR; for (i = 100; i; i--) { err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES); if (err) break; - if (cmd.resp[0] & MMC_CARD_BUSY || ocr == 0) + /* if we're just probing, do a single pass */ + if (ocr == 0) break; + /* otherwise wait until reset completes */ + if (mmc_host_is_spi(host)) { + /* + * Both R1_SPI_IDLE and MMC_CARD_BUSY indicate + * an initialized card under SPI, but some cards + * (Marvell's) only behave when looking at this + * one. + */ + if (cmd.resp[1] & MMC_CARD_BUSY) + break; + } else { + if (cmd.resp[0] & MMC_CARD_BUSY) + break; + } + err = -ETIMEDOUT; mmc_delay(10); } if (rocr) - *rocr = cmd.resp[0]; + *rocr = cmd.resp[mmc_host_is_spi(host) ? 1 : 0]; return err; } @@ -68,21 +84,29 @@ int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn, cmd.arg |= (write && out) ? 0x08000000 : 0x00000000; cmd.arg |= addr << 9; cmd.arg |= in; - cmd.flags = MMC_RSP_R5 | MMC_CMD_AC; + cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_AC; err = mmc_wait_for_cmd(card->host, &cmd, 0); if (err) return err; - if (cmd.resp[0] & R5_ERROR) - return -EIO; - if (cmd.resp[0] & R5_FUNCTION_NUMBER) - return -EINVAL; - if (cmd.resp[0] & R5_OUT_OF_RANGE) - return -ERANGE; + if (mmc_host_is_spi(card->host)) { + /* host driver already reported errors */ + } else { + if (cmd.resp[0] & R5_ERROR) + return -EIO; + if (cmd.resp[0] & R5_FUNCTION_NUMBER) + return -EINVAL; + if (cmd.resp[0] & R5_OUT_OF_RANGE) + return -ERANGE; + } - if (out) - *out = cmd.resp[0] & 0xFF; + if (out) { + if (mmc_host_is_spi(card->host)) + *out = (cmd.resp[0] >> 8) & 0xFF; + else + *out = cmd.resp[0] & 0xFF; + } return 0; } @@ -117,7 +141,7 @@ int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn, cmd.arg |= (blksz == 512) ? 0 : blksz; /* byte mode */ else cmd.arg |= 0x08000000 | blocks; /* block mode */ - cmd.flags = MMC_RSP_R5 | MMC_CMD_ADTC; + cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC; data.blksz = blksz; data.blocks = blocks; @@ -136,12 +160,16 @@ int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn, if (data.error) return data.error; - if (cmd.resp[0] & R5_ERROR) - return -EIO; - if (cmd.resp[0] & R5_FUNCTION_NUMBER) - return -EINVAL; - if (cmd.resp[0] & R5_OUT_OF_RANGE) - return -ERANGE; + if (mmc_host_is_spi(card->host)) { + /* host driver already reported errors */ + } else { + if (cmd.resp[0] & R5_ERROR) + return -EIO; + if (cmd.resp[0] & R5_FUNCTION_NUMBER) + return -EINVAL; + if (cmd.resp[0] & R5_OUT_OF_RANGE) + return -ERANGE; + } return 0; } -- cgit v1.2.3 From 15a0580ced081a0f7dc2deea8a4812bdc5e9a109 Mon Sep 17 00:00:00 2001 From: David Brownell Date: Wed, 8 Aug 2007 09:12:54 -0700 Subject: mmc_spi host driver This is the latest version of the MMC-over-SPI support. It works on 2.6.23-rc2 plus git-mmc (from rc1-mm2), along with the preceding patches which teach the rest of the MMC stack about SPI. The main issue of note is that sometimes cards need to be power cycled to recover after certain faults. Also, it may sometimes be necessary to disable CRCs. ("modprobe mmc_core use_spi_crc=n") Signed-off-by: David Brownell Cc: mikael.starvik@axis.com, Cc: Hans-Peter Nilsson Cc: Jan Nikitenko Cc: Mike Lavender Signed-off-by: Pierre Ossman --- MAINTAINERS | 6 + drivers/mmc/host/Kconfig | 13 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/mmc_spi.c | 1408 +++++++++++++++++++++++++++++++++++++++++++ include/linux/spi/mmc_spi.h | 33 + 5 files changed, 1461 insertions(+) create mode 100644 drivers/mmc/host/mmc_spi.c create mode 100644 include/linux/spi/mmc_spi.h (limited to 'drivers') diff --git a/MAINTAINERS b/MAINTAINERS index c22d34b11f6..0df29ce05e5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2567,6 +2567,12 @@ M: drzeus-mmc@drzeus.cx L: linux-kernel@vger.kernel.org S: Maintained +MULTIMEDIA CARD (MMC) ETC. OVER SPI +P: David Brownell +M: dbrownell@users.sourceforge.net +L: linux-kernel@vger.kernel.org +S: Odd fixes + MULTISOUND SOUND DRIVER P: Andrew Veliath M: andrewtv@usa.net diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index e23082fe88d..68fb052afd3 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -100,3 +100,16 @@ config MMC_TIFM_SD To compile this driver as a module, choose M here: the module will be called tifm_sd. +config MMC_SPI + tristate "MMC/SD over SPI (EXPERIMENTAL)" + depends on MMC && SPI_MASTER && !HIGHMEM && EXPERIMENTAL + select CRC7 + select CRC_ITU_T + help + Some systems accss MMC/SD cards using a SPI controller instead of + using a "native" MMC/SD controller. This has a disadvantage of + being relatively high overhead, but a compensating advantage of + working on many systems without dedicated MMC/SD controllers. + + If unsure, or if your system has no SPI master driver, say N. + diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 6685f64345b..8dc82ce647b 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -15,4 +15,5 @@ obj-$(CONFIG_MMC_AU1X) += au1xmmc.o obj-$(CONFIG_MMC_OMAP) += omap.o obj-$(CONFIG_MMC_AT91) += at91_mci.o obj-$(CONFIG_MMC_TIFM_SD) += tifm_sd.o +obj-$(CONFIG_MMC_SPI) += mmc_spi.o diff --git a/drivers/mmc/host/mmc_spi.c b/drivers/mmc/host/mmc_spi.c new file mode 100644 index 00000000000..f30327bba6f --- /dev/null +++ b/drivers/mmc/host/mmc_spi.c @@ -0,0 +1,1408 @@ +/* + * mmc_spi.c - Access SD/MMC cards through SPI master controllers + * + * (C) Copyright 2005, Intec Automation, + * Mike Lavender (mike@steroidmicros) + * (C) Copyright 2006-2007, David Brownell + * (C) Copyright 2007, Axis Communications, + * Hans-Peter Nilsson (hp@axis.com) + * (C) Copyright 2007, ATRON electronic GmbH, + * Jan Nikitenko + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include +#include +#include +#include +#include +#include + +#include +#include /* for R1_SPI_* bit values */ + +#include +#include + +#include + + +/* NOTES: + * + * - For now, we won't try to interoperate with a real mmc/sd/sdio + * controller, although some of them do have hardware support for + * SPI protocol. The main reason for such configs would be mmc-ish + * cards like DataFlash, which don't support that "native" protocol. + * + * We don't have a "DataFlash/MMC/SD/SDIO card slot" abstraction to + * switch between driver stacks, and in any case if "native" mode + * is available, it will be faster and hence preferable. + * + * - MMC depends on a different chipselect management policy than the + * SPI interface currently supports for shared bus segments: it needs + * to issue multiple spi_message requests with the chipselect active, + * using the results of one message to decide the next one to issue. + * + * Pending updates to the programming interface, this driver expects + * that it not share the bus with other drivers (precluding conflicts). + * + * - We tell the controller to keep the chipselect active from the + * beginning of an mmc_host_ops.request until the end. So beware + * of SPI controller drivers that mis-handle the cs_change flag! + * + * However, many cards seem OK with chipselect flapping up/down + * during that time ... at least on unshared bus segments. + */ + + +/* + * Local protocol constants, internal to data block protocols. + */ + +/* Response tokens used to ack each block written: */ +#define SPI_MMC_RESPONSE_CODE(x) ((x) & 0x1f) +#define SPI_RESPONSE_ACCEPTED ((2 << 1)|1) +#define SPI_RESPONSE_CRC_ERR ((5 << 1)|1) +#define SPI_RESPONSE_WRITE_ERR ((6 << 1)|1) + +/* Read and write blocks start with these tokens and end with crc; + * on error, read tokens act like a subset of R2_SPI_* values. + */ +#define SPI_TOKEN_SINGLE 0xfe /* single block r/w, multiblock read */ +#define SPI_TOKEN_MULTI_WRITE 0xfc /* multiblock write */ +#define SPI_TOKEN_STOP_TRAN 0xfd /* terminate multiblock write */ + +#define MMC_SPI_BLOCKSIZE 512 + + +/* These fixed timeouts come from the latest SD specs, which say to ignore + * the CSD values. The R1B value is for card erase (e.g. the "I forgot the + * card's password" scenario); it's mostly applied to STOP_TRANSMISSION after + * reads which takes nowhere near that long. Older cards may be able to use + * shorter timeouts ... but why bother? + */ +#define readblock_timeout ktime_set(0, 100 * 1000 * 1000) +#define writeblock_timeout ktime_set(0, 250 * 1000 * 1000) +#define r1b_timeout ktime_set(3, 0) + + +/****************************************************************************/ + +/* + * Local Data Structures + */ + +/* "scratch" is per-{command,block} data exchanged with the card */ +struct scratch { + u8 status[29]; + u8 data_token; + __be16 crc_val; +}; + +struct mmc_spi_host { + struct mmc_host *mmc; + struct spi_device *spi; + + unsigned char power_mode; + u16 powerup_msecs; + + struct mmc_spi_platform_data *pdata; + + /* for bulk data transfers */ + struct spi_transfer token, t, crc, early_status; + struct spi_message m; + + /* for status readback */ + struct spi_transfer status; + struct spi_message readback; + + /* underlying DMA-aware controller, or null */ + struct device *dma_dev; + + /* buffer used for commands and for message "overhead" */ + struct scratch *data; + dma_addr_t data_dma; + + /* Specs say to write ones most of the time, even when the card + * has no need to read its input data; and many cards won't care. + * This is our source of those ones. + */ + void *ones; + dma_addr_t ones_dma; +}; + + +/****************************************************************************/ + +/* + * MMC-over-SPI protocol glue, used by the MMC stack interface + */ + +static inline int mmc_cs_off(struct mmc_spi_host *host) +{ + /* chipselect will always be inactive after setup() */ + return spi_setup(host->spi); +} + +static int +mmc_spi_readbytes(struct mmc_spi_host *host, unsigned len) +{ + int status; + + if (len > sizeof(*host->data)) { + WARN_ON(1); + return -EIO; + } + + host->status.len = len; + + if (host->dma_dev) + dma_sync_single_for_device(host->dma_dev, + host->data_dma, sizeof(*host->data), + DMA_FROM_DEVICE); + + status = spi_sync(host->spi, &host->readback); + if (status == 0) + status = host->readback.status; + + if (host->dma_dev) + dma_sync_single_for_cpu(host->dma_dev, + host->data_dma, sizeof(*host->data), + DMA_FROM_DEVICE); + + return status; +} + +static int +mmc_spi_skip(struct mmc_spi_host *host, ktime_t timeout, unsigned n, u8 byte) +{ + u8 *cp = host->data->status; + + timeout = ktime_add(timeout, ktime_get()); + + while (1) { + int status; + unsigned i; + + status = mmc_spi_readbytes(host, n); + if (status < 0) + return status; + + for (i = 0; i < n; i++) { + if (cp[i] != byte) + return cp[i]; + } + + /* REVISIT investigate msleep() to avoid busy-wait I/O + * in at least some cases. + */ + if (ktime_to_ns(ktime_sub(ktime_get(), timeout)) > 0) + break; + } + return -ETIMEDOUT; +} + +static inline int +mmc_spi_wait_unbusy(struct mmc_spi_host *host, ktime_t timeout) +{ + return mmc_spi_skip(host, timeout, sizeof(host->data->status), 0); +} + +static int mmc_spi_readtoken(struct mmc_spi_host *host) +{ + return mmc_spi_skip(host, readblock_timeout, 1, 0xff); +} + + +/* + * Note that for SPI, cmd->resp[0] is not the same data as "native" protocol + * hosts return! The low byte holds R1_SPI bits. The next byte may hold + * R2_SPI bits ... for SEND_STATUS, or after data read errors. + * + * cmd->resp[1] holds any four-byte response, for R3 (READ_OCR) and on + * newer cards R7 (IF_COND). + */ + +static char *maptype(struct mmc_command *cmd) +{ + switch (mmc_spi_resp_type(cmd)) { + case MMC_RSP_SPI_R1: return "R1"; + case MMC_RSP_SPI_R1B: return "R1B"; + case MMC_RSP_SPI_R2: return "R2/R5"; + case MMC_RSP_SPI_R3: return "R3/R4/R7"; + default: return "?"; + } +} + +/* return zero, else negative errno after setting cmd->error */ +static int mmc_spi_response_get(struct mmc_spi_host *host, + struct mmc_command *cmd, int cs_on) +{ + u8 *cp = host->data->status; + u8 *end = cp + host->t.len; + int value = 0; + char tag[32]; + + snprintf(tag, sizeof(tag), " ... CMD%d response SPI_%s", + cmd->opcode, maptype(cmd)); + + /* Except for data block reads, the whole response will already + * be stored in the scratch buffer. It's somewhere after the + * command and the first byte we read after it. We ignore that + * first byte. After STOP_TRANSMISSION command it may include + * two data bits, but otherwise it's all ones. + */ + cp += 8; + while (cp < end && *cp == 0xff) + cp++; + + /* Data block reads (R1 response types) may need more data... */ + if (cp == end) { + unsigned i; + + cp = host->data->status; + + /* Card sends N(CR) (== 1..8) bytes of all-ones then one + * status byte ... and we already scanned 2 bytes. + * + * REVISIT block read paths use nasty byte-at-a-time I/O + * so it can always DMA directly into the target buffer. + * It'd probably be better to memcpy() the first chunk and + * avoid extra i/o calls... + */ + for (i = 2; i < 9; i++) { + value = mmc_spi_readbytes(host, 1); + if (value < 0) + goto done; + if (*cp != 0xff) + goto checkstatus; + } + value = -ETIMEDOUT; + goto done; + } + +checkstatus: + if (*cp & 0x80) { + dev_dbg(&host->spi->dev, "%s: INVALID RESPONSE, %02x\n", + tag, *cp); + value = -EBADR; + goto done; + } + + cmd->resp[0] = *cp++; + cmd->error = 0; + + /* Status byte: the entire seven-bit R1 response. */ + if (cmd->resp[0] != 0) { + if ((R1_SPI_PARAMETER | R1_SPI_ADDRESS + | R1_SPI_ILLEGAL_COMMAND) + & cmd->resp[0]) + value = -EINVAL; + else if (R1_SPI_COM_CRC & cmd->resp[0]) + value = -EILSEQ; + else if ((R1_SPI_ERASE_SEQ | R1_SPI_ERASE_RESET) + & cmd->resp[0]) + value = -EIO; + /* else R1_SPI_IDLE, "it's resetting" */ + } + + switch (mmc_spi_resp_type(cmd)) { + + /* SPI R1B == R1 + busy; STOP_TRANSMISSION (for multiblock reads) + * and less-common stuff like various erase operations. + */ + case MMC_RSP_SPI_R1B: + /* maybe we read all the busy tokens already */ + while (cp < end && *cp == 0) + cp++; + if (cp == end) + mmc_spi_wait_unbusy(host, r1b_timeout); + break; + + /* SPI R2 == R1 + second status byte; SEND_STATUS + * SPI R5 == R1 + data byte; IO_RW_DIRECT + */ + case MMC_RSP_SPI_R2: + cmd->resp[0] |= *cp << 8; + break; + + /* SPI R3, R4, or R7 == R1 + 4 bytes */ + case MMC_RSP_SPI_R3: + cmd->resp[1] = be32_to_cpu(get_unaligned((u32 *)cp)); + break; + + /* SPI R1 == just one status byte */ + case MMC_RSP_SPI_R1: + break; + + default: + dev_dbg(&host->spi->dev, "bad response type %04x\n", + mmc_spi_resp_type(cmd)); + if (value >= 0) + value = -EINVAL; + goto done; + } + + if (value < 0) + dev_dbg(&host->spi->dev, "%s: resp %04x %08x\n", + tag, cmd->resp[0], cmd->resp[1]); + + /* disable chipselect on errors and some success cases */ + if (value >= 0 && cs_on) + return value; +done: + if (value < 0) + cmd->error = value; + mmc_cs_off(host); + return value; +} + +/* Issue command and read its response. + * Returns zero on success, negative for error. + * + * On error, caller must cope with mmc core retry mechanism. That + * means immediate low-level resubmit, which affects the bus lock... + */ +static int +mmc_spi_command_send(struct mmc_spi_host *host, + struct mmc_request *mrq, + struct mmc_command *cmd, int cs_on) +{ + struct scratch *data = host->data; + u8 *cp = data->status; + u32 arg = cmd->arg; + int status; + struct spi_transfer *t; + + /* We can handle most commands (except block reads) in one full + * duplex I/O operation before either starting the next transfer + * (data block or command) or else deselecting the card. + * + * First, write 7 bytes: + * - an all-ones byte to ensure the card is ready + * - opcode byte (plus start and transmission bits) + * - four bytes of big-endian argument + * - crc7 (plus end bit) ... always computed, it's cheap + * + * We init the whole buffer to all-ones, which is what we need + * to write while we're reading (later) response data. + */ + memset(cp++, 0xff, sizeof(data->status)); + + *cp++ = 0x40 | cmd->opcode; + *cp++ = (u8)(arg >> 24); + *cp++ = (u8)(arg >> 16); + *cp++ = (u8)(arg >> 8); + *cp++ = (u8)arg; + *cp++ = (crc7(0, &data->status[1], 5) << 1) | 0x01; + + /* Then, read up to 13 bytes (while writing all-ones): + * - N(CR) (== 1..8) bytes of all-ones + * - status byte (for all response types) + * - the rest of the response, either: + * + nothing, for R1 or R1B responses + * + second status byte, for R2 responses + * + four data bytes, for R3 and R7 responses + * + * Finally, read some more bytes ... in the nice cases we know in + * advance how many, and reading 1 more is always OK: + * - N(EC) (== 0..N) bytes of all-ones, before deselect/finish + * - N(RC) (== 1..N) bytes of all-ones, before next command + * - N(WR) (== 1..N) bytes of all-ones, before data write + * + * So in those cases one full duplex I/O of at most 21 bytes will + * handle the whole command, leaving the card ready to receive a + * data block or new command. We do that whenever we can, shaving + * CPU and IRQ costs (especially when using DMA or FIFOs). + * + * There are two other cases, where it's not generally practical + * to rely on a single I/O: + * + * - R1B responses need at least N(EC) bytes of all-zeroes. + * + * In this case we can *try* to fit it into one I/O, then + * maybe read more data later. + * + * - Data block reads are more troublesome, since a variable + * number of padding bytes precede the token and data. + * + N(CX) (== 0..8) bytes of all-ones, before CSD or CID + * + N(AC) (== 1..many) bytes of all-ones + * + * In this case we currently only have minimal speedups here: + * when N(CR) == 1 we can avoid I/O in response_get(). + */ + if (cs_on && (mrq->data->flags & MMC_DATA_READ)) { + cp += 2; /* min(N(CR)) + status */ + /* R1 */ + } else { + cp += 10; /* max(N(CR)) + status + min(N(RC),N(WR)) */ + if (cmd->flags & MMC_RSP_SPI_S2) /* R2/R5 */ + cp++; + else if (cmd->flags & MMC_RSP_SPI_B4) /* R3/R4/R7 */ + cp += 4; + else if (cmd->flags & MMC_RSP_BUSY) /* R1B */ + cp = data->status + sizeof(data->status); + /* else: R1 (most commands) */ + } + + dev_dbg(&host->spi->dev, " mmc_spi: CMD%d, resp %s\n", + cmd->opcode, maptype(cmd)); + + /* send command, leaving chipselect active */ + spi_message_init(&host->m); + + t = &host->t; + memset(t, 0, sizeof(*t)); + t->tx_buf = t->rx_buf = data->status; + t->tx_dma = t->rx_dma = host->data_dma; + t->len = cp - data->status; + t->cs_change = 1; + spi_message_add_tail(t, &host->m); + + if (host->dma_dev) { + host->m.is_dma_mapped = 1; + dma_sync_single_for_device(host->dma_dev, + host->data_dma, sizeof(*host->data), + DMA_BIDIRECTIONAL); + } + status = spi_sync(host->spi, &host->m); + if (status == 0) + status = host->m.status; + + if (host->dma_dev) + dma_sync_single_for_cpu(host->dma_dev, + host->data_dma, sizeof(*host->data), + DMA_BIDIRECTIONAL); + if (status < 0) { + dev_dbg(&host->spi->dev, " ... write returned %d\n", status); + cmd->error = status; + return status; + } + + /* after no-data commands and STOP_TRANSMISSION, chipselect off */ + return mmc_spi_response_get(host, cmd, cs_on); +} + +/* Build data message with up to four separate transfers. For TX, we + * start by writing the data token. And in most cases, we finish with + * a status transfer. + * + * We always provide TX data for data and CRC. The MMC/SD protocol + * requires us to write ones; but Linux defaults to writing zeroes; + * so we explicitly initialize it to all ones on RX paths. + * + * We also handle DMA mapping, so the underlying SPI controller does + * not need to (re)do it for each message. + */ +static void +mmc_spi_setup_data_message( + struct mmc_spi_host *host, + int multiple, + enum dma_data_direction direction) +{ + struct spi_transfer *t; + struct scratch *scratch = host->data; + dma_addr_t dma = host->data_dma; + + spi_message_init(&host->m); + if (dma) + host->m.is_dma_mapped = 1; + + /* for reads, readblock() skips 0xff bytes before finding + * the token; for writes, this transfer issues that token. + */ + if (direction == DMA_TO_DEVICE) { + t = &host->token; + memset(t, 0, sizeof(*t)); + t->len = 1; + if (multiple) + scratch->data_token = SPI_TOKEN_MULTI_WRITE; + else + scratch->data_token = SPI_TOKEN_SINGLE; + t->tx_buf = &scratch->data_token; + if (dma) + t->tx_dma = dma + offsetof(struct scratch, data_token); + spi_message_add_tail(t, &host->m); + } + + /* Body of transfer is buffer, then CRC ... + * either TX-only, or RX with TX-ones. + */ + t = &host->t; + memset(t, 0, sizeof(*t)); + t->tx_buf = host->ones; + t->tx_dma = host->ones_dma; + /* length and actual buffer info are written later */ + spi_message_add_tail(t, &host->m); + + t = &host->crc; + memset(t, 0, sizeof(*t)); + t->len = 2; + if (direction == DMA_TO_DEVICE) { + /* the actual CRC may get written later */ + t->tx_buf = &scratch->crc_val; + if (dma) + t->tx_dma = dma + offsetof(struct scratch, crc_val); + } else { + t->tx_buf = host->ones; + t->tx_dma = host->ones_dma; + t->rx_buf = &scratch->crc_val; + if (dma) + t->rx_dma = dma + offsetof(struct scratch, crc_val); + } + spi_message_add_tail(t, &host->m); + + /* + * A single block read is followed by N(EC) [0+] all-ones bytes + * before deselect ... don't bother. + * + * Multiblock reads are followed by N(AC) [1+] all-ones bytes before + * the next block is read, or a STOP_TRANSMISSION is issued. We'll + * collect that single byte, so readblock() doesn't need to. + * + * For a write, the one-byte data response follows immediately, then + * come zero or more busy bytes, then N(WR) [1+] all-ones bytes. + * Then single block reads may deselect, and multiblock ones issue + * the next token (next data block, or STOP_TRAN). We can try to + * minimize I/O ops by using a single read to collect end-of-busy. + */ + if (multiple || direction == DMA_TO_DEVICE) { + t = &host->early_status; + memset(t, 0, sizeof(*t)); + t->len = (direction == DMA_TO_DEVICE) + ? sizeof(scratch->status) + : 1; + t->tx_buf = host->ones; + t->tx_dma = host->ones_dma; + t->rx_buf = scratch->status; + if (dma) + t->rx_dma = dma + offsetof(struct scratch, status); + t->cs_change = 1; + spi_message_add_tail(t, &host->m); + } +} + +/* + * Write one block: + * - caller handled preceding N(WR) [1+] all-ones bytes + * - data block + * + token + * + data bytes + * + crc16 + * - an all-ones byte ... card writes a data-response byte + * - followed by N(EC) [0+] all-ones bytes, card writes zero/'busy' + * + * Return negative errno, else success. + */ +static int +mmc_spi_writeblock(struct mmc_spi_host *host, struct spi_transfer *t) +{ + struct spi_device *spi = host->spi; + int status, i; + struct scratch *scratch = host->data; + + if (host->mmc->use_spi_crc) + scratch->crc_val = cpu_to_be16( + crc_itu_t(0, t->tx_buf, t->len)); + if (host->dma_dev) + dma_sync_single_for_device(host->dma_dev, + host->data_dma, sizeof(*scratch), + DMA_BIDIRECTIONAL); + + status = spi_sync(spi, &host->m); + if (status == 0) + status = host->m.status; + + if (status != 0) { + dev_dbg(&spi->dev, "write error (%d)\n", status); + return status; + } + + if (host->dma_dev) + dma_sync_single_for_cpu(host->dma_dev, + host->data_dma, sizeof(*scratch), + DMA_BIDIRECTIONAL); + + /* + * Get the transmission data-response reply. It must follow + * immediately after the data block we transferred. This reply + * doesn't necessarily tell whether the write operation succeeded; + * it just says if the transmission was ok and whether *earlier* + * writes succeeded; see the standard. + */ + switch (SPI_MMC_RESPONSE_CODE(scratch->status[0])) { + case SPI_RESPONSE_ACCEPTED: + status = 0; + break; + case SPI_RESPONSE_CRC_ERR: + /* host shall then issue MMC_STOP_TRANSMISSION */ + status = -EILSEQ; + break; + case SPI_RESPONSE_WRITE_ERR: + /* host shall then issue MMC_STOP_TRANSMISSION, + * and should MMC_SEND_STATUS to sort it out + */ + status = -EIO; + break; + default: + status = -EPROTO; + break; + } + if (status != 0) { + dev_dbg(&spi->dev, "write error %02x (%d)\n", + scratch->status[0], status); + return status; + } + + t->tx_buf += t->len; + if (host->dma_dev) + t->tx_dma += t->len; + + /* Return when not busy. If we didn't collect that status yet, + * we'll need some more I/O. + */ + for (i = 1; i < sizeof(scratch->status); i++) { + if (scratch->status[i] != 0) + return 0; + } + return mmc_spi_wait_unbusy(host, writeblock_timeout); +} + +/* + * Read one block: + * - skip leading all-ones bytes ... either + * + N(AC) [1..f(clock,CSD)] usually, else + * + N(CX) [0..8] when reading CSD or CID + * - data block + * + token ... if error token, no data or crc + * + data bytes + * + crc16 + * + * After single block reads, we're done; N(EC) [0+] all-ones bytes follow + * before dropping chipselect. + * + * For multiblock reads, caller either reads the next block or issues a + * STOP_TRANSMISSION command. + */ +static int +mmc_spi_readblock(struct mmc_spi_host *host, struct spi_transfer *t) +{ + struct spi_device *spi = host->spi; + int status; + struct scratch *scratch = host->data; + + /* At least one SD card sends an all-zeroes byte when N(CX) + * applies, before the all-ones bytes ... just cope with that. + */ + status = mmc_spi_readbytes(host, 1); + if (status < 0) + return status; + status = scratch->status[0]; + if (status == 0xff || status == 0) + status = mmc_spi_readtoken(host); + + if (status == SPI_TOKEN_SINGLE) { + if (host->dma_dev) { + dma_sync_single_for_device(host->dma_dev, + host->data_dma, sizeof(*scratch), + DMA_BIDIRECTIONAL); + dma_sync_single_for_device(host->dma_dev, + t->rx_dma, t->len, + DMA_FROM_DEVICE); + } + + status = spi_sync(spi, &host->m); + if (status == 0) + status = host->m.status; + + if (host->dma_dev) { + dma_sync_single_for_cpu(host->dma_dev, + host->data_dma, sizeof(*scratch), + DMA_BIDIRECTIONAL); + dma_sync_single_for_cpu(host->dma_dev, + t->rx_dma, t->len, + DMA_FROM_DEVICE); + } + + } else { + dev_dbg(&spi->dev, "read error %02x (%d)\n", status, status); + + /* we've read extra garbage, timed out, etc */ + if (status < 0) + return status; + + /* low four bits are an R2 subset, fifth seems to be + * vendor specific ... map them all to generic error.. + */ + return -EIO; + } + + if (host->mmc->use_spi_crc) { + u16 crc = crc_itu_t(0, t->rx_buf, t->len); + + be16_to_cpus(&scratch->crc_val); + if (scratch->crc_val != crc) { + dev_dbg(&spi->dev, "read - crc error: crc_val=0x%04x, " + "computed=0x%04x len=%d\n", + scratch->crc_val, crc, t->len); + return -EILSEQ; + } + } + + t->rx_buf += t->len; + if (host->dma_dev) + t->rx_dma += t->len; + + return 0; +} + +/* + * An MMC/SD data stage includes one or more blocks, optional CRCs, + * and inline handshaking. That handhaking makes it unlike most + * other SPI protocol stacks. + */ +static void +mmc_spi_data_do(struct mmc_spi_host *host, struct mmc_command *cmd, + struct mmc_data *data, u32 blk_size) +{ + struct spi_device *spi = host->spi; + struct device *dma_dev = host->dma_dev; + struct spi_transfer *t; + enum dma_data_direction direction; + struct scatterlist *sg; + unsigned n_sg; + int multiple = (data->blocks > 1); + + if (data->flags & MMC_DATA_READ) + direction = DMA_FROM_DEVICE; + else + direction = DMA_TO_DEVICE; + mmc_spi_setup_data_message(host, multiple, direction); + t = &host->t; + + /* Handle scatterlist segments one at a time, with synch for + * each 512-byte block + */ + for (sg = data->sg, n_sg = data->sg_len; n_sg; n_sg--, sg++) { + int status = 0; + dma_addr_t dma_addr = 0; + void *kmap_addr; + unsigned length = sg->length; + enum dma_data_direction dir = direction; + + /* set up dma mapping for controller drivers that might + * use DMA ... though they may fall back to PIO + */ + if (dma_dev) { + /* never invalidate whole *shared* pages ... */ + if ((sg->offset != 0 || length != PAGE_SIZE) + && dir == DMA_FROM_DEVICE) + dir = DMA_BIDIRECTIONAL; + + dma_addr = dma_map_page(dma_dev, sg->page, 0, + PAGE_SIZE, dir); + if (direction == DMA_TO_DEVICE) + t->tx_dma = dma_addr + sg->offset; + else + t->rx_dma = dma_addr + sg->offset; + } + + /* allow pio too; we don't allow highmem */ + kmap_addr = kmap(sg->page); + if (direction == DMA_TO_DEVICE) + t->tx_buf = kmap_addr + sg->offset; + else + t->rx_buf = kmap_addr + sg->offset; + + /* transfer each block, and update request status */ + while (length) { + t->len = min(length, blk_size); + + dev_dbg(&host->spi->dev, + " mmc_spi: %s block, %d bytes\n", + (direction == DMA_TO_DEVICE) + ? "write" + : "read", + t->len); + + if (direction == DMA_TO_DEVICE) + status = mmc_spi_writeblock(host, t); + else + status = mmc_spi_readblock(host, t); + if (status < 0) + break; + + data->bytes_xfered += t->len; + length -= t->len; + + if (!multiple) + break; + } + + /* discard mappings */ + if (direction == DMA_FROM_DEVICE) + flush_kernel_dcache_page(sg->page); + kunmap(sg->page); + if (dma_dev) + dma_unmap_page(dma_dev, dma_addr, PAGE_SIZE, dir); + + if (status < 0) { + data->error = status; + dev_dbg(&spi->dev, "%s status %d\n", + (direction == DMA_TO_DEVICE) + ? "write" : "read", + status); + break; + } + } + + /* NOTE some docs describe an MMC-only SET_BLOCK_COUNT (CMD23) that + * can be issued before multiblock writes. Unlike its more widely + * documented analogue for SD cards (SET_WR_BLK_ERASE_COUNT, ACMD23), + * that can affect the STOP_TRAN logic. Complete (and current) + * MMC specs should sort that out before Linux starts using CMD23. + */ + if (direction == DMA_TO_DEVICE && multiple) { + struct scratch *scratch = host->data; + int tmp; + const unsigned statlen = sizeof(scratch->status); + + dev_dbg(&spi->dev, " mmc_spi: STOP_TRAN\n"); + + /* Tweak the per-block message we set up earlier by morphing + * it to hold single buffer with the token followed by some + * all-ones bytes ... skip N(BR) (0..1), scan the rest for + * "not busy any longer" status, and leave chip selected. + */ + INIT_LIST_HEAD(&host->m.transfers); + list_add(&host->early_status.transfer_list, + &host->m.transfers); + + memset(scratch->status, 0xff, statlen); + scratch->status[0] = SPI_TOKEN_STOP_TRAN; + + host->early_status.tx_buf = host->early_status.rx_buf; + host->early_status.tx_dma = host->early_status.rx_dma; + host->early_status.len = statlen; + + if (host->dma_dev) + dma_sync_single_for_device(host->dma_dev, + host->data_dma, sizeof(*scratch), + DMA_BIDIRECTIONAL); + + tmp = spi_sync(spi, &host->m); + if (tmp == 0) + tmp = host->m.status; + + if (host->dma_dev) + dma_sync_single_for_cpu(host->dma_dev, + host->data_dma, sizeof(*scratch), + DMA_BIDIRECTIONAL); + + if (tmp < 0) { + if (!data->error) + data->error = tmp; + return; + } + + /* Ideally we collected "not busy" status with one I/O, + * avoiding wasteful byte-at-a-time scanning... but more + * I/O is often needed. + */ + for (tmp = 2; tmp < statlen; tmp++) { + if (scratch->status[tmp] != 0) + return; + } + tmp = mmc_spi_wait_unbusy(host, writeblock_timeout); + if (tmp < 0 && !data->error) + data->error = tmp; + } +} + +/****************************************************************************/ + +/* + * MMC driver implementation -- the interface to the MMC stack + */ + +static void mmc_spi_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct mmc_spi_host *host = mmc_priv(mmc); + int status = -EINVAL; + +#ifdef DEBUG + /* MMC core and layered drivers *MUST* issue SPI-aware commands */ + { + struct mmc_command *cmd; + int invalid = 0; + + cmd = mrq->cmd; + if (!mmc_spi_resp_type(cmd)) { + dev_dbg(&host->spi->dev, "bogus command\n"); + cmd->error = -EINVAL; + invalid = 1; + } + + cmd = mrq->stop; + if (cmd && !mmc_spi_resp_type(cmd)) { + dev_dbg(&host->spi->dev, "bogus STOP command\n"); + cmd->error = -EINVAL; + invalid = 1; + } + + if (invalid) { + dump_stack(); + mmc_request_done(host->mmc, mrq); + return; + } + } +#endif + + /* issue command; then optionally data and stop */ + status = mmc_spi_command_send(host, mrq, mrq->cmd, mrq->data != NULL); + if (status == 0 && mrq->data) { + mmc_spi_data_do(host, mrq->cmd, mrq->data, mrq->data->blksz); + if (mrq->stop) + status = mmc_spi_command_send(host, mrq, mrq->stop, 0); + else + mmc_cs_off(host); + } + + mmc_request_done(host->mmc, mrq); +} + +/* See Section 6.4.1, in SD "Simplified Physical Layer Specification 2.0" + * + * NOTE that here we can't know that the card has just been powered up; + * not all MMC/SD sockets support power switching. + * + * FIXME when the card is still in SPI mode, e.g. from a previous kernel, + * this doesn't seem to do the right thing at all... + */ +static void mmc_spi_initsequence(struct mmc_spi_host *host) +{ + /* Try to be very sure any previous command has completed; + * wait till not-busy, skip debris from any old commands. + */ + mmc_spi_wait_unbusy(host, r1b_timeout); + mmc_spi_readbytes(host, 10); + + /* + * Do a burst with chipselect active-high. We need to do this to + * meet the requirement of 74 clock cycles with both chipselect + * and CMD (MOSI) high before CMD0 ... after the card has been + * powered up to Vdd(min), and so is ready to take commands. + * + * Some cards are particularly needy of this (e.g. Viking "SD256") + * while most others don't seem to care. + * + * Note that this is one of the places MMC/SD plays games with the + * SPI protocol. Another is that when chipselect is released while + * the card returns BUSY status, the clock must issue several cycles + * with chipselect high before the card will stop driving its output. + */ + host->spi->mode |= SPI_CS_HIGH; + if (spi_setup(host->spi) != 0) { + /* Just warn; most cards work without it. */ + dev_warn(&host->spi->dev, + "can't change chip-select polarity\n"); + host->spi->mode &= ~SPI_CS_HIGH; + } else { + mmc_spi_readbytes(host, 18); + + host->spi->mode &= ~SPI_CS_HIGH; + if (spi_setup(host->spi) != 0) { + /* Wot, we can't get the same setup we had before? */ + dev_err(&host->spi->dev, + "can't restore chip-select polarity\n"); + } + } +} + +static char *mmc_powerstring(u8 power_mode) +{ + switch (power_mode) { + case MMC_POWER_OFF: return "off"; + case MMC_POWER_UP: return "up"; + case MMC_POWER_ON: return "on"; + } + return "?"; +} + +static void mmc_spi_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct mmc_spi_host *host = mmc_priv(mmc); + + if (host->power_mode != ios->power_mode) { + int canpower; + + canpower = host->pdata && host->pdata->setpower; + + dev_dbg(&host->spi->dev, "mmc_spi: power %s (%d)%s\n", + mmc_powerstring(ios->power_mode), + ios->vdd, + canpower ? ", can switch" : ""); + + /* switch power on/off if possible, accounting for + * max 250msec powerup time if needed. + */ + if (canpower) { + switch (ios->power_mode) { + case MMC_POWER_OFF: + case MMC_POWER_UP: + host->pdata->setpower(&host->spi->dev, + ios->vdd); + if (ios->power_mode == MMC_POWER_UP) + msleep(host->powerup_msecs); + } + } + + /* See 6.4.1 in the simplified SD card physical spec 2.0 */ + if (ios->power_mode == MMC_POWER_ON) + mmc_spi_initsequence(host); + + /* If powering down, ground all card inputs to avoid power + * delivery from data lines! On a shared SPI bus, this + * will probably be temporary; 6.4.2 of the simplified SD + * spec says this must last at least 1msec. + * + * - Clock low means CPOL 0, e.g. mode 0 + * - MOSI low comes from writing zero + * - Chipselect is usually active low... + */ + if (canpower && ios->power_mode == MMC_POWER_OFF) { + int mres; + + host->spi->mode &= ~(SPI_CPOL|SPI_CPHA); + mres = spi_setup(host->spi); + if (mres < 0) + dev_dbg(&host->spi->dev, + "switch to SPI mode 0 failed\n"); + + if (spi_w8r8(host->spi, 0x00) < 0) + dev_dbg(&host->spi->dev, + "put spi signals to low failed\n"); + + /* + * Now clock should be low due to spi mode 0; + * MOSI should be low because of written 0x00; + * chipselect should be low (it is active low) + * power supply is off, so now MMC is off too! + * + * FIXME no, chipselect can be high since the + * device is inactive and SPI_CS_HIGH is clear... + */ + msleep(10); + if (mres == 0) { + host->spi->mode |= (SPI_CPOL|SPI_CPHA); + mres = spi_setup(host->spi); + if (mres < 0) + dev_dbg(&host->spi->dev, + "switch back to SPI mode 3" + " failed\n"); + } + } + + host->power_mode = ios->power_mode; + } + + if (host->spi->max_speed_hz != ios->clock && ios->clock != 0) { + int status; + + host->spi->max_speed_hz = ios->clock; + status = spi_setup(host->spi); + dev_dbg(&host->spi->dev, + "mmc_spi: clock to %d Hz, %d\n", + host->spi->max_speed_hz, status); + } +} + +static int mmc_spi_get_ro(struct mmc_host *mmc) +{ + struct mmc_spi_host *host = mmc_priv(mmc); + + if (host->pdata && host->pdata->get_ro) + return host->pdata->get_ro(mmc->parent); + /* board doesn't support read only detection; assume writeable */ + return 0; +} + + +static const struct mmc_host_ops mmc_spi_ops = { + .request = mmc_spi_request, + .set_ios = mmc_spi_set_ios, + .get_ro = mmc_spi_get_ro, +}; + + +/****************************************************************************/ + +/* + * SPI driver implementation + */ + +static irqreturn_t +mmc_spi_detect_irq(int irq, void *mmc) +{ + struct mmc_spi_host *host = mmc_priv(mmc); + u16 delay_msec = max(host->pdata->detect_delay, (u16)100); + + mmc_detect_change(mmc, msecs_to_jiffies(delay_msec)); + return IRQ_HANDLED; +} + +static int mmc_spi_probe(struct spi_device *spi) +{ + void *ones; + struct mmc_host *mmc; + struct mmc_spi_host *host; + int status; + + /* MMC and SD specs only seem to care that sampling is on the + * rising edge ... meaning SPI modes 0 or 3. So either SPI mode + * should be legit. We'll use mode 0 since it seems to be a + * bit less troublesome on some hardware ... unclear why. + */ + spi->mode = SPI_MODE_0; + spi->bits_per_word = 8; + + status = spi_setup(spi); + if (status < 0) { + dev_dbg(&spi->dev, "needs SPI mode %02x, %d KHz; %d\n", + spi->mode, spi->max_speed_hz / 1000, + status); + return status; + } + + /* We can use the bus safely iff nobody else will interfere with + * us. That is, either we have the experimental exclusive access + * primitives ... or else there's nobody to share it with. + */ + if (spi->master->num_chipselect > 1) { + struct device *parent = spi->dev.parent; + + /* If there are multiple devices on this bus, we + * can't proceed. + */ + spin_lock(&parent->klist_children.k_lock); + if (parent->klist_children.k_list.next + != parent->klist_children.k_list.prev) + status = -EMLINK; + else + status = 0; + spin_unlock(&parent->klist_children.k_lock); + if (status < 0) { + dev_err(&spi->dev, "can't share SPI bus\n"); + return status; + } + + /* REVISIT we can't guarantee another device won't + * be added later. It's uncommon though ... for now, + * work as if this is safe. + */ + dev_warn(&spi->dev, "ASSUMING unshared SPI bus!\n"); + } + + /* We need a supply of ones to transmit. This is the only time + * the CPU touches these, so cache coherency isn't a concern. + * + * NOTE if many systems use more than one MMC-over-SPI connector + * it'd save some memory to share this. That's evidently rare. + */ + status = -ENOMEM; + ones = kmalloc(MMC_SPI_BLOCKSIZE, GFP_KERNEL); + if (!ones) + goto nomem; + memset(ones, 0xff, MMC_SPI_BLOCKSIZE); + + mmc = mmc_alloc_host(sizeof(*host), &spi->dev); + if (!mmc) + goto nomem; + + mmc->ops = &mmc_spi_ops; + mmc->max_blk_size = MMC_SPI_BLOCKSIZE; + + /* As long as we keep track of the number of successfully + * transmitted blocks, we're good for multiwrite. + */ + mmc->caps = MMC_CAP_SPI | MMC_CAP_MULTIWRITE; + + /* SPI doesn't need the lowspeed device identification thing for + * MMC or SD cards, since it never comes up in open drain mode. + * That's good; some SPI masters can't handle very low speeds! + * + * However, low speed SDIO cards need not handle over 400 KHz; + * that's the only reason not to use a few MHz for f_min (until + * the upper layer reads the target frequency from the CSD). + */ + mmc->f_min = 400000; + mmc->f_max = spi->max_speed_hz; + + host = mmc_priv(mmc); + host->mmc = mmc; + host->spi = spi; + + host->ones = ones; + + /* Platform data is used to hook up things like card sensing + * and power switching gpios. + */ + host->pdata = spi->dev.platform_data; + if (host->pdata) + mmc->ocr_avail = host->pdata->ocr_mask; + if (!mmc->ocr_avail) { + dev_warn(&spi->dev, "ASSUMING 3.2-3.4 V slot power\n"); + mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34; + } + if (host->pdata && host->pdata->setpower) { + host->powerup_msecs = host->pdata->powerup_msecs; + if (!host->powerup_msecs || host->powerup_msecs > 250) + host->powerup_msecs = 250; + } + + dev_set_drvdata(&spi->dev, mmc); + + /* preallocate dma buffers */ + host->data = kmalloc(sizeof(*host->data), GFP_KERNEL); + if (!host->data) + goto fail_nobuf1; + + if (spi->master->cdev.dev->dma_mask) { + struct device *dev = spi->master->cdev.dev; + + host->dma_dev = dev; + host->ones_dma = dma_map_single(dev, ones, + MMC_SPI_BLOCKSIZE, DMA_TO_DEVICE); + host->data_dma = dma_map_single(dev, host->data, + sizeof(*host->data), DMA_BIDIRECTIONAL); + + /* REVISIT in theory those map operations can fail... */ + + dma_sync_single_for_cpu(host->dma_dev, + host->data_dma, sizeof(*host->data), + DMA_BIDIRECTIONAL); + } + + /* setup message for status/busy readback */ + spi_message_init(&host->readback); + host->readback.is_dma_mapped = (host->dma_dev != NULL); + + spi_message_add_tail(&host->status, &host->readback); + host->status.tx_buf = host->ones; + host->status.tx_dma = host->ones_dma; + host->status.rx_buf = &host->data->status; + host->status.rx_dma = host->data_dma + offsetof(struct scratch, status); + host->status.cs_change = 1; + + /* register card detect irq */ + if (host->pdata && host->pdata->init) { + status = host->pdata->init(&spi->dev, mmc_spi_detect_irq, mmc); + if (status != 0) + goto fail_glue_init; + } + + status = mmc_add_host(mmc); + if (status != 0) + goto fail_add_host; + + dev_info(&spi->dev, "SD/MMC host %s%s%s%s\n", + mmc->class_dev.bus_id, + host->dma_dev ? "" : ", no DMA", + (host->pdata && host->pdata->get_ro) + ? "" : ", no WP", + (host->pdata && host->pdata->setpower) + ? "" : ", no poweroff"); + return 0; + +fail_add_host: + mmc_remove_host (mmc); +fail_glue_init: + if (host->dma_dev) + dma_unmap_single(host->dma_dev, host->data_dma, + sizeof(*host->data), DMA_BIDIRECTIONAL); + kfree(host->data); + +fail_nobuf1: + mmc_free_host(mmc); + dev_set_drvdata(&spi->dev, NULL); + +nomem: + kfree(ones); + return status; +} + + +static int __devexit mmc_spi_remove(struct spi_device *spi) +{ + struct mmc_host *mmc = dev_get_drvdata(&spi->dev); + struct mmc_spi_host *host; + + if (mmc) { + host = mmc_priv(mmc); + + /* prevent new mmc_detect_change() calls */ + if (host->pdata && host->pdata->exit) + host->pdata->exit(&spi->dev, mmc); + + mmc_remove_host(mmc); + + if (host->dma_dev) { + dma_unmap_single(host->dma_dev, host->ones_dma, + MMC_SPI_BLOCKSIZE, DMA_TO_DEVICE); + dma_unmap_single(host->dma_dev, host->data_dma, + sizeof(*host->data), DMA_BIDIRECTIONAL); + } + + kfree(host->data); + kfree(host->ones); + + spi->max_speed_hz = mmc->f_max; + mmc_free_host(mmc); + dev_set_drvdata(&spi->dev, NULL); + } + return 0; +} + + +static struct spi_driver mmc_spi_driver = { + .driver = { + .name = "mmc_spi", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = mmc_spi_probe, + .remove = __devexit_p(mmc_spi_remove), +}; + + +static int __init mmc_spi_init(void) +{ + return spi_register_driver(&mmc_spi_driver); +} +module_init(mmc_spi_init); + + +static void __exit mmc_spi_exit(void) +{ + spi_unregister_driver(&mmc_spi_driver); +} +module_exit(mmc_spi_exit); + + +MODULE_AUTHOR("Mike Lavender, David Brownell, " + "Hans-Peter Nilsson, Jan Nikitenko"); +MODULE_DESCRIPTION("SPI SD/MMC host driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/spi/mmc_spi.h b/include/linux/spi/mmc_spi.h new file mode 100644 index 00000000000..e9bbe3ebd72 --- /dev/null +++ b/include/linux/spi/mmc_spi.h @@ -0,0 +1,33 @@ +#ifndef __LINUX_SPI_MMC_SPI_H +#define __LINUX_SPI_MMC_SPI_H + +struct device; +struct mmc_host; + +/* Put this in platform_data of a device being used to manage an MMC/SD + * card slot. (Modeled after PXA mmc glue; see that for usage examples.) + * + * REVISIT This is not a spi-specific notion. Any card slot should be + * able to handle it. If the MMC core doesn't adopt this kind of notion, + * switch the "struct device *" parameters over to "struct spi_device *". + */ +struct mmc_spi_platform_data { + /* driver activation and (optional) card detect irq hookup */ + int (*init)(struct device *, + irqreturn_t (*)(int, void *), + void *); + void (*exit)(struct device *, void *); + + /* sense switch on sd cards */ + int (*get_ro)(struct device *); + + /* how long to debounce card detect, in msecs */ + u16 detect_delay; + + /* power management */ + u16 powerup_msecs; /* delay of up to 250 msec */ + u32 ocr_mask; /* available voltages */ + void (*setpower)(struct device *, unsigned int maskval); +}; + +#endif /* __LINUX_SPI_MMC_SPI_H */ -- cgit v1.2.3 From af8350c756cb48a738474738f7bf8c0e572fa057 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 24 Sep 2007 07:15:48 +0200 Subject: mmc: add led trigger Add a led trigger for each host controller that indicates if there is a request active on the controller. Signed-off-by: Pierre Ossman --- drivers/mmc/core/core.c | 5 +++++ drivers/mmc/core/host.c | 5 +++++ include/linux/mmc/host.h | 6 ++++++ 3 files changed, 16 insertions(+) (limited to 'drivers') diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index bad39442f8f..09435e0ec68 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -92,6 +93,8 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) cmd->error = 0; host->ops->request(host, mrq); } else { + led_trigger_event(host->led, LED_OFF); + pr_debug("%s: req done (CMD%u): %d: %08x %08x %08x %08x\n", mmc_hostname(host), cmd->opcode, err, cmd->resp[0], cmd->resp[1], @@ -146,6 +149,8 @@ mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) WARN_ON(!host->claimed); + led_trigger_event(host->led, LED_FULL); + mrq->cmd->error = 0; mrq->cmd->mrq = mrq; if (mrq->data) { diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 37b761891d6..64fbc9759a3 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -15,6 +15,7 @@ #include #include #include +#include #include @@ -115,6 +116,8 @@ int mmc_add_host(struct mmc_host *host) snprintf(host->class_dev.bus_id, BUS_ID_SIZE, "mmc%d", host->index); + led_trigger_register_simple(host->class_dev.bus_id, &host->led); + err = device_add(&host->class_dev); if (err) return err; @@ -140,6 +143,8 @@ void mmc_remove_host(struct mmc_host *host) device_del(&host->class_dev); + led_trigger_unregister(host->led); + spin_lock(&mmc_host_lock); idr_remove(&mmc_host_idr, host->index); spin_unlock(&mmc_host_lock); diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 76eef94782f..125eee1407f 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -10,6 +10,8 @@ #ifndef LINUX_MMC_HOST_H #define LINUX_MMC_HOST_H +#include + #include struct mmc_ios { @@ -133,6 +135,10 @@ struct mmc_host { struct task_struct *sdio_irq_thread; atomic_t sdio_irq_thread_abort; +#ifdef CONFIG_LEDS_TRIGGERS + struct led_trigger *led; /* activity led */ +#endif + unsigned long private[0] ____cacheline_aligned; }; -- cgit v1.2.3 From faf39ede5e6325d3e91b6e4e0017d27fbecb6022 Mon Sep 17 00:00:00 2001 From: Pavel Pisa Date: Sun, 23 Sep 2007 22:59:01 +0200 Subject: arm: i.MX/MX1 SDHC implements SD cards read-only switch read-back The patch enables to define MMC host get_ro() method through platform data. Signed-off-by: Pavel Pisa Signed-off-by: Pierre Ossman --- arch/arm/mach-imx/mx1ads.c | 2 +- drivers/mmc/host/imxmmc.c | 16 ++++++++++++++-- include/asm-arm/arch-imx/mmc.h | 5 ++++- 3 files changed, 19 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/arch/arm/mach-imx/mx1ads.c b/arch/arm/mach-imx/mx1ads.c index da893c80d47..a9778c1587a 100644 --- a/arch/arm/mach-imx/mx1ads.c +++ b/arch/arm/mach-imx/mx1ads.c @@ -116,7 +116,7 @@ static struct platform_device *devices[] __initdata = { }; #ifdef CONFIG_MMC_IMX -static int mx1ads_mmc_card_present(void) +static int mx1ads_mmc_card_present(struct device *dev) { /* MMC/SD Card Detect is PB 20 on MX1ADS V1.0.7 */ return (SSR(1) & (1 << 20) ? 0 : 1); diff --git a/drivers/mmc/host/imxmmc.c b/drivers/mmc/host/imxmmc.c index e33c123c702..6ebc41e7592 100644 --- a/drivers/mmc/host/imxmmc.c +++ b/drivers/mmc/host/imxmmc.c @@ -884,9 +884,21 @@ static void imxmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) } } +static int imxmci_get_ro(struct mmc_host *mmc) +{ + struct imxmci_host *host = mmc_priv(mmc); + + if (host->pdata && host->pdata->get_ro) + return host->pdata->get_ro(mmc_dev(mmc)); + /* Host doesn't support read only detection so assume writeable */ + return 0; +} + + static const struct mmc_host_ops imxmci_ops = { .request = imxmci_request, .set_ios = imxmci_set_ios, + .get_ro = imxmci_get_ro, }; static struct resource *platform_device_resource(struct platform_device *dev, unsigned int mask, int nr) @@ -913,7 +925,7 @@ static void imxmci_check_status(unsigned long data) { struct imxmci_host *host = (struct imxmci_host *)data; - if( host->pdata->card_present() != host->present ) { + if( host->pdata->card_present(mmc_dev(host->mmc)) != host->present ) { host->present ^= 1; dev_info(mmc_dev(host->mmc), "card %s\n", host->present ? "inserted" : "removed"); @@ -1022,7 +1034,7 @@ static int imxmci_probe(struct platform_device *pdev) if (ret) goto out; - host->present = host->pdata->card_present(); + host->present = host->pdata->card_present(mmc_dev(mmc)); init_timer(&host->timer); host->timer.data = (unsigned long)host; host->timer.function = imxmci_check_status; diff --git a/include/asm-arm/arch-imx/mmc.h b/include/asm-arm/arch-imx/mmc.h index 84c726934ac..4712f354dcc 100644 --- a/include/asm-arm/arch-imx/mmc.h +++ b/include/asm-arm/arch-imx/mmc.h @@ -3,8 +3,11 @@ #include +struct device; + struct imxmmc_platform_data { - int (*card_present)(void); + int (*card_present)(struct device *); + int (*get_ro)(struct device *); }; extern void imx_set_mmc_info(struct imxmmc_platform_data *info); -- cgit v1.2.3 From 81ab570f65395f09962f4bca0d89403f8911c071 Mon Sep 17 00:00:00 2001 From: Bridge Wu Date: Tue, 25 Sep 2007 18:59:07 +0200 Subject: mmc: pxamci: better pending IRQ determination Pending interrupts should be determined from both I_REG and I_MASK registers. Signed-off-by: Bridge Wu Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman --- drivers/mmc/host/pxamci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/mmc/host/pxamci.c b/drivers/mmc/host/pxamci.c index b89e32d1e9b..03d16288115 100644 --- a/drivers/mmc/host/pxamci.c +++ b/drivers/mmc/host/pxamci.c @@ -298,7 +298,7 @@ static irqreturn_t pxamci_irq(int irq, void *devid) unsigned int ireg; int handled = 0; - ireg = readl(host->base + MMC_I_REG); + ireg = readl(host->base + MMC_I_REG) & ~readl(host->base + MMC_I_MASK); if (ireg) { unsigned stat = readl(host->base + MMC_STAT); -- cgit v1.2.3 From fe2dc44eac1223a0e92859242f58fd2a58a6f8fa Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Mon, 24 Sep 2007 15:47:18 -0400 Subject: mmc: pxamci: set proper block capabilities according to PXA flavor From PXA27x, it is possible to do 2048-byte block transfers. Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman --- drivers/mmc/host/pxamci.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/host/pxamci.c b/drivers/mmc/host/pxamci.c index 03d16288115..67acc6a0089 100644 --- a/drivers/mmc/host/pxamci.c +++ b/drivers/mmc/host/pxamci.c @@ -444,9 +444,9 @@ static int pxamci_probe(struct platform_device *pdev) mmc->max_seg_size = PAGE_SIZE; /* - * Block length register is 10 bits. + * Block length register is only 10 bits before PXA27x. */ - mmc->max_blk_size = 1023; + mmc->max_blk_size = (cpu_is_pxa21x() || cpu_is_pxa25x()) ? 1023 : 2048; /* * Block count register is 16 bits. -- cgit v1.2.3 From df456f479aa6fdc812df51627c6f2c21d8a1aed8 Mon Sep 17 00:00:00 2001 From: Bridge Wu Date: Tue, 25 Sep 2007 19:09:19 +0200 Subject: mmc: pxamci: set proper buswidth capabilities according to PXA flavor From PXA27x, it is possible to do 4-bit data transfers. Signed-off-by: Bridge Wu Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman --- drivers/mmc/host/pxamci.c | 10 +++++++++- drivers/mmc/host/pxamci.h | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/mmc/host/pxamci.c b/drivers/mmc/host/pxamci.c index 67acc6a0089..6549f546df5 100644 --- a/drivers/mmc/host/pxamci.c +++ b/drivers/mmc/host/pxamci.c @@ -284,7 +284,7 @@ static int pxamci_data_done(struct pxamci_host *host, unsigned int stat) host->data = NULL; if (host->mrq->stop) { pxamci_stop_clock(host); - pxamci_start_cmd(host, host->mrq->stop, 0); + pxamci_start_cmd(host, host->mrq->stop, host->cmdat); } else { pxamci_finish_request(host, host->mrq); } @@ -382,6 +382,11 @@ static void pxamci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) host->cmdat |= CMDAT_INIT; } + if (ios->bus_width == MMC_BUS_WIDTH_4) + host->cmdat |= CMDAT_SD_4DAT; + else + host->cmdat &= ~CMDAT_SD_4DAT; + pr_debug("PXAMCI: clkrt = %x cmdat = %x\n", host->clkrt, host->cmdat); } @@ -460,6 +465,9 @@ static int pxamci_probe(struct platform_device *pdev) mmc->ocr_avail = host->pdata ? host->pdata->ocr_mask : MMC_VDD_32_33|MMC_VDD_33_34; + mmc->caps = 0; + if (!cpu_is_pxa21x() && !cpu_is_pxa25x()) + mmc->caps |= MMC_CAP_4_BIT_DATA; host->sg_cpu = dma_alloc_coherent(&pdev->dev, PAGE_SIZE, &host->sg_dma, GFP_KERNEL); if (!host->sg_cpu) { diff --git a/drivers/mmc/host/pxamci.h b/drivers/mmc/host/pxamci.h index df17c281278..5655be823a0 100644 --- a/drivers/mmc/host/pxamci.h +++ b/drivers/mmc/host/pxamci.h @@ -25,6 +25,7 @@ #define SPI_EN (1 << 0) #define MMC_CMDAT 0x0010 +#define CMDAT_SD_4DAT (1 << 8) #define CMDAT_DMAEN (1 << 7) #define CMDAT_INIT (1 << 6) #define CMDAT_BUSY (1 << 5) -- cgit v1.2.3 From 5d3ad4e8a12e538eead0a37d22b1ba6aec0f2127 Mon Sep 17 00:00:00 2001 From: Bridge Wu Date: Tue, 25 Sep 2007 19:11:00 +0200 Subject: mmc: pxamci: add SDIO card interrupt reporting capability Again, only available from PXA27x. Signed-off-by: Bridge Wu Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman --- drivers/mmc/host/pxamci.c | 28 +++++++++++++++++++++++----- drivers/mmc/host/pxamci.h | 1 + 2 files changed, 24 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/host/pxamci.c b/drivers/mmc/host/pxamci.c index 6549f546df5..91e25683b39 100644 --- a/drivers/mmc/host/pxamci.c +++ b/drivers/mmc/host/pxamci.c @@ -309,6 +309,10 @@ static irqreturn_t pxamci_irq(int irq, void *devid) handled |= pxamci_cmd_done(host, stat); if (ireg & DATA_TRAN_DONE) handled |= pxamci_data_done(host, stat); + if (ireg & SDIO_INT) { + mmc_signal_sdio_irq(host->mmc); + handled = 1; + } } return IRQ_RETVAL(handled); @@ -391,10 +395,21 @@ static void pxamci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) host->clkrt, host->cmdat); } +static void pxamci_enable_sdio_irq(struct mmc_host *host, int enable) +{ + struct pxamci_host *pxa_host = mmc_priv(host); + + if (enable) + pxamci_enable_irq(pxa_host, SDIO_INT); + else + pxamci_disable_irq(pxa_host, SDIO_INT); +} + static const struct mmc_host_ops pxamci_ops = { - .request = pxamci_request, - .get_ro = pxamci_get_ro, - .set_ios = pxamci_set_ios, + .request = pxamci_request, + .get_ro = pxamci_get_ro, + .set_ios = pxamci_set_ios, + .enable_sdio_irq = pxamci_enable_sdio_irq, }; static void pxamci_dma_irq(int dma, void *devid) @@ -466,8 +481,11 @@ static int pxamci_probe(struct platform_device *pdev) host->pdata->ocr_mask : MMC_VDD_32_33|MMC_VDD_33_34; mmc->caps = 0; - if (!cpu_is_pxa21x() && !cpu_is_pxa25x()) - mmc->caps |= MMC_CAP_4_BIT_DATA; + host->cmdat = 0; + if (!cpu_is_pxa21x() && !cpu_is_pxa25x()) { + mmc->caps |= MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ; + host->cmdat |= CMDAT_SDIO_INT_EN; + } host->sg_cpu = dma_alloc_coherent(&pdev->dev, PAGE_SIZE, &host->sg_dma, GFP_KERNEL); if (!host->sg_cpu) { diff --git a/drivers/mmc/host/pxamci.h b/drivers/mmc/host/pxamci.h index 5655be823a0..3153e779d46 100644 --- a/drivers/mmc/host/pxamci.h +++ b/drivers/mmc/host/pxamci.h @@ -25,6 +25,7 @@ #define SPI_EN (1 << 0) #define MMC_CMDAT 0x0010 +#define CMDAT_SDIO_INT_EN (1 << 11) #define CMDAT_SD_4DAT (1 << 8) #define CMDAT_DMAEN (1 << 7) #define CMDAT_INIT (1 << 6) -- cgit v1.2.3 From 6f4285d13300f1c8cd675a41ab390cea06173cd1 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Thu, 27 Sep 2007 10:48:29 +0200 Subject: sdio: adaptive interrupt polling The interrupt polling frequency is a compromise between power usage and interrupt latency. Unfortunately, it affects throughput rather severely for devices which require an interrupt for every chunk of data. By making the polling frequency adaptive, we get better throughput with those devices without sacficing too much power. Polling will quickly increase when there is an actual interrupt, and slowly fall back to the idle frequency when the interrupts stop coming. Signed-off-by: Pierre Ossman --- drivers/mmc/core/sdio_irq.c | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/core/sdio_irq.c b/drivers/mmc/core/sdio_irq.c index 8843a4c2fe9..f78ffeefed5 100644 --- a/drivers/mmc/core/sdio_irq.c +++ b/drivers/mmc/core/sdio_irq.c @@ -27,7 +27,7 @@ static int process_sdio_pending_irqs(struct mmc_card *card) { - int i, ret; + int i, ret, count; unsigned char pending; ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_INTx, 0, &pending); @@ -37,6 +37,7 @@ static int process_sdio_pending_irqs(struct mmc_card *card) return ret; } + count = 0; for (i = 1; i <= 7; i++) { if (pending & (1 << i)) { struct sdio_func *func = card->sdio_func[i - 1]; @@ -46,20 +47,21 @@ static int process_sdio_pending_irqs(struct mmc_card *card) sdio_func_id(func)); } else if (func->irq_handler) { func->irq_handler(func); + count++; } else printk(KERN_WARNING "%s: pending IRQ with no handler\n", sdio_func_id(func)); } } - return 0; + return count; } static int sdio_irq_thread(void *_host) { struct mmc_host *host = _host; struct sched_param param = { .sched_priority = 1 }; - unsigned long period; + unsigned long period, idle_period; int ret; sched_setscheduler(current, SCHED_FIFO, ¶m); @@ -70,8 +72,9 @@ static int sdio_irq_thread(void *_host) * asynchronous notification of pending SDIO card interrupts * hence we poll for them in that case. */ + idle_period = msecs_to_jiffies(10); period = (host->caps & MMC_CAP_SDIO_IRQ) ? - MAX_SCHEDULE_TIMEOUT : msecs_to_jiffies(10); + MAX_SCHEDULE_TIMEOUT : idle_period; pr_debug("%s: IRQ thread started (poll period = %lu jiffies)\n", mmc_hostname(host), period); @@ -101,9 +104,24 @@ static int sdio_irq_thread(void *_host) * errors. FIXME: determine if due to card removal and * possibly exit this thread if so. */ - if (ret) + if (ret < 0) ssleep(1); + /* + * Adaptive polling frequency based on the assumption + * that an interrupt will be closely followed by more. + * This has a substantial benefit for network devices. + */ + if (!(host->caps & MMC_CAP_SDIO_IRQ)) { + if (ret > 0) + period /= 2; + else { + period++; + if (period > idle_period) + period = idle_period; + } + } + set_task_state(current, TASK_INTERRUPTIBLE); if (host->caps & MMC_CAP_SDIO_IRQ) host->ops->enable_sdio_irq(host, 1); -- cgit v1.2.3 From 5ae70296c85f96a9969891d9de3410ebdf210b71 Mon Sep 17 00:00:00 2001 From: Philip Langdale Date: Sat, 15 Sep 2007 12:54:08 -0700 Subject: mmc: Disabler for Ricoh MMC controller Thanks to Matt Domsch and Rezwanul Kabir at Dell, we know how to disable the MMC controller on the multi-function Ricoh R5C832. The MMC controller needs to be disabled or it will steal MMC cards from the SD controller where they would otherwise be supported by the Linux SDHCI driver. Signed-off-by: Philipl Langdale Signed-off-by: Pierre Ossman --- drivers/mmc/host/Kconfig | 17 +++++ drivers/mmc/host/Makefile | 1 + drivers/mmc/host/ricoh_mmc.c | 151 +++++++++++++++++++++++++++++++++++++++++++ include/linux/pci_ids.h | 2 + 4 files changed, 171 insertions(+) create mode 100644 drivers/mmc/host/ricoh_mmc.c (limited to 'drivers') diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 68fb052afd3..5fef6783c71 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -35,6 +35,23 @@ config MMC_SDHCI If unsure, say N. +config MMC_RICOH_MMC + tristate "Ricoh MMC Controller Disabler (EXPERIMENTAL)" + depends on PCI && EXPERIMENTAL && MMC_SDHCI + help + This selects the disabler for the Ricoh MMC Controller. This + proprietary controller is unnecessary because the SDHCI driver + supports MMC cards on the SD controller, but if it is not + disabled, it will steal the MMC cards away - rendering them + useless. It is safe to select this driver even if you don't + have a Ricoh based card reader. + + + To compile this driver as a module, choose M here: + the module will be called ricoh_mmc. + + If unsure, say Y. + config MMC_OMAP tristate "TI OMAP Multimedia Card Interface support" depends on ARCH_OMAP diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 8dc82ce647b..3877c87e6da 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_MMC_ARMMMCI) += mmci.o obj-$(CONFIG_MMC_PXA) += pxamci.o obj-$(CONFIG_MMC_IMX) += imxmmc.o obj-$(CONFIG_MMC_SDHCI) += sdhci.o +obj-$(CONFIG_MMC_RICOH_MMC) += ricoh_mmc.o obj-$(CONFIG_MMC_WBSD) += wbsd.o obj-$(CONFIG_MMC_AU1X) += au1xmmc.o obj-$(CONFIG_MMC_OMAP) += omap.o diff --git a/drivers/mmc/host/ricoh_mmc.c b/drivers/mmc/host/ricoh_mmc.c new file mode 100644 index 00000000000..1e8704533bc --- /dev/null +++ b/drivers/mmc/host/ricoh_mmc.c @@ -0,0 +1,151 @@ +/* + * ricoh_mmc.c - Dummy driver to disable the Rioch MMC controller. + * + * Copyright (C) 2007 Philip Langdale, All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +/* + * This is a conceptually ridiculous driver, but it is required by the way + * the Ricoh multi-function R5C832 works. This chip implements firewire + * and four different memory card controllers. Two of those controllers are + * an SDHCI controller and a proprietary MMC controller. The linux SDHCI + * driver supports MMC cards but the chip detects MMC cards in hardware + * and directs them to the MMC controller - so the SDHCI driver never sees + * them. To get around this, we must disable the useless MMC controller. + * At that point, the SDHCI controller will start seeing them. As a bonus, + * a detection event occurs immediately, even if the MMC card is already + * in the reader. + * + * The relevant registers live on the firewire function, so this is unavoidably + * ugly. Such is life. + */ + +#include + +#define DRIVER_NAME "ricoh-mmc" + +static const struct pci_device_id pci_ids[] __devinitdata = { + { + .vendor = PCI_VENDOR_ID_RICOH, + .device = PCI_DEVICE_ID_RICOH_R5C843, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + { /* end: all zeroes */ }, +}; + +MODULE_DEVICE_TABLE(pci, pci_ids); + +static int __devinit ricoh_mmc_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + u8 rev; + + struct pci_dev *fw_dev = NULL; + + BUG_ON(pdev == NULL); + BUG_ON(ent == NULL); + + pci_read_config_byte(pdev, PCI_CLASS_REVISION, &rev); + + printk(KERN_INFO DRIVER_NAME + ": Ricoh MMC controller found at %s [%04x:%04x] (rev %x)\n", + pci_name(pdev), (int)pdev->vendor, (int)pdev->device, + (int)rev); + + while ((fw_dev = pci_get_device(PCI_VENDOR_ID_RICOH, PCI_DEVICE_ID_RICOH_R5C832, fw_dev))) { + if (PCI_SLOT(pdev->devfn) == PCI_SLOT(fw_dev->devfn) && + pdev->bus == fw_dev->bus) { + u8 write_enable; + u8 disable; + + pci_read_config_byte(fw_dev, 0xCB, &disable); + if (disable & 0x02) { + printk(KERN_INFO DRIVER_NAME + ": Controller already disabled. Nothing to do.\n"); + return -ENODEV; + } + + pci_read_config_byte(fw_dev, 0xCA, &write_enable); + pci_write_config_byte(fw_dev, 0xCA, 0x57); + pci_write_config_byte(fw_dev, 0xCB, disable | 0x02); + pci_write_config_byte(fw_dev, 0xCA, write_enable); + + pci_set_drvdata(pdev, fw_dev); + + printk(KERN_INFO DRIVER_NAME + ": Controller is now disabled.\n"); + + break; + } + } + + if (pci_get_drvdata(pdev) == NULL) { + printk(KERN_WARNING DRIVER_NAME + ": Main firewire function not found. Cannot disable controller.\n"); + return -ENODEV; + } + + return 0; +} + +static void __devexit ricoh_mmc_remove(struct pci_dev *pdev) +{ + u8 write_enable; + u8 disable; + struct pci_dev *fw_dev = NULL; + + fw_dev = pci_get_drvdata(pdev); + BUG_ON(fw_dev == NULL); + + pci_read_config_byte(fw_dev, 0xCA, &write_enable); + pci_read_config_byte(fw_dev, 0xCB, &disable); + pci_write_config_byte(fw_dev, 0xCA, 0x57); + pci_write_config_byte(fw_dev, 0xCB, disable & ~0x02); + pci_write_config_byte(fw_dev, 0xCA, write_enable); + + printk(KERN_INFO DRIVER_NAME + ": Controller is now re-enabled.\n"); + + pci_set_drvdata(pdev, NULL); +} + +static struct pci_driver ricoh_mmc_driver = { + .name = DRIVER_NAME, + .id_table = pci_ids, + .probe = ricoh_mmc_probe, + .remove = __devexit_p(ricoh_mmc_remove), +}; + +/*****************************************************************************\ + * * + * Driver init/exit * + * * +\*****************************************************************************/ + +static int __init ricoh_mmc_drv_init(void) +{ + printk(KERN_INFO DRIVER_NAME + ": Ricoh MMC Controller disabling driver\n"); + printk(KERN_INFO DRIVER_NAME ": Copyright(c) Philip Langdale\n"); + + return pci_register_driver(&ricoh_mmc_driver); +} + +static void __exit ricoh_mmc_drv_exit(void) +{ + pci_unregister_driver(&ricoh_mmc_driver); +} + +module_init(ricoh_mmc_drv_init); +module_exit(ricoh_mmc_drv_exit); + +MODULE_AUTHOR("Philip Langdale "); +MODULE_DESCRIPTION("Ricoh MMC Controller disabling driver"); +MODULE_LICENSE("GPL"); + diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 55f307ffbf9..c8636bb3e3a 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -1471,6 +1471,8 @@ #define PCI_DEVICE_ID_RICOH_RL5C476 0x0476 #define PCI_DEVICE_ID_RICOH_RL5C478 0x0478 #define PCI_DEVICE_ID_RICOH_R5C822 0x0822 +#define PCI_DEVICE_ID_RICOH_R5C832 0x0832 +#define PCI_DEVICE_ID_RICOH_R5C843 0x0843 #define PCI_VENDOR_ID_DLINK 0x1186 #define PCI_DEVICE_ID_DLINK_DGE510T 0x4c00 -- cgit v1.2.3 From 2df3b71b2746469b5b344cf7da5facecd4110cc9 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Sat, 29 Sep 2007 10:46:20 -0400 Subject: sdhci: fix a typo Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman --- drivers/mmc/host/sdhci.c | 2 +- drivers/mmc/host/sdhci.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 228ce42371d..317578d6853 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -132,7 +132,7 @@ static void sdhci_dumpregs(struct sdhci_host *host) readb(host->ioaddr + SDHCI_POWER_CONTROL), readb(host->ioaddr + SDHCI_BLOCK_GAP_CONTROL)); printk(KERN_DEBUG DRIVER_NAME ": Wake-up: 0x%08x | Clock: 0x%08x\n", - readb(host->ioaddr + SDHCI_WALK_UP_CONTROL), + readb(host->ioaddr + SDHCI_WAKE_UP_CONTROL), readw(host->ioaddr + SDHCI_CLOCK_CONTROL)); printk(KERN_DEBUG DRIVER_NAME ": Timeout: 0x%08x | Int stat: 0x%08x\n", readb(host->ioaddr + SDHCI_TIMEOUT_CONTROL), diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index e28987d6d2e..05195ea900f 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -81,7 +81,7 @@ #define SDHCI_BLOCK_GAP_CONTROL 0x2A -#define SDHCI_WALK_UP_CONTROL 0x2B +#define SDHCI_WAKE_UP_CONTROL 0x2B #define SDHCI_CLOCK_CONTROL 0x2C #define SDHCI_DIVIDER_SHIFT 8 -- cgit v1.2.3 From 56e71efe44eb06ae1761f43cca70a5f3cc54c0fb Mon Sep 17 00:00:00 2001 From: Feng Tang Date: Sat, 29 Sep 2007 14:15:05 +0800 Subject: sdhci: remove DMA capability check from controller's PCI Class reg Many host controllers don't fully follow the PCI spec to claim whether they support DMA in PCI class register. Leave the driver to judge it from the Capability register, quirks and module parameter Signed-off-by: Feng Tang Signed-off-by: Pierre Ossman --- drivers/mmc/host/sdhci.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 317578d6853..866528c4ac1 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -1299,13 +1299,18 @@ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot) host->flags |= SDHCI_USE_DMA; } else if (chip->quirks & SDHCI_QUIRK_FORCE_DMA) host->flags |= SDHCI_USE_DMA; - else if ((pdev->class & 0x0000FF) != PCI_SDHCI_IFDMA) - DBG("Controller doesn't have DMA interface\n"); else if (!(caps & SDHCI_CAN_DO_DMA)) DBG("Controller doesn't have DMA capability\n"); else host->flags |= SDHCI_USE_DMA; + if (((pdev->class & 0x0000FF) != PCI_SDHCI_IFDMA) && + (host->flags & SDHCI_USE_DMA)) { + printk(KERN_WARNING "%s: Will use DMA " + "mode even though HW doesn't fully " + "claim to support it.\n", host->slot_descr); + } + if (host->flags & SDHCI_USE_DMA) { if (pci_set_dma_mask(pdev, DMA_32BIT_MASK)) { printk(KERN_WARNING "%s: No suitable DMA available. " -- cgit v1.2.3 From 7c168e3db7d900008ee304574057e0dc1a8505af Mon Sep 17 00:00:00 2001 From: Feng Tang Date: Sun, 30 Sep 2007 12:44:18 +0200 Subject: sdhci: add SDHCI_QUIRK_BROKEN_DMA quirk Some controllers like ENE712 can't work properly with DMA mode, add this quirk to force these kinds of HW work with PIO mode Signed-off-by: Feng Tang Signed-off-by: Pierre Ossman --- drivers/mmc/host/sdhci.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 866528c4ac1..2b4a86c55a9 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -35,6 +35,7 @@ static unsigned int debug_quirks = 0; #define SDHCI_QUIRK_NO_CARD_NO_RESET (1<<2) #define SDHCI_QUIRK_SINGLE_POWER_WRITE (1<<3) #define SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS (1<<4) +#define SDHCI_QUIRK_BROKEN_DMA (1<<5) static const struct pci_device_id pci_ids[] __devinitdata = { { @@ -68,7 +69,8 @@ static const struct pci_device_id pci_ids[] __devinitdata = { .device = PCI_DEVICE_ID_ENE_CB712_SD, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, - .driver_data = SDHCI_QUIRK_SINGLE_POWER_WRITE, + .driver_data = SDHCI_QUIRK_SINGLE_POWER_WRITE | + SDHCI_QUIRK_BROKEN_DMA, }, { @@ -76,7 +78,8 @@ static const struct pci_device_id pci_ids[] __devinitdata = { .device = PCI_DEVICE_ID_ENE_CB712_SD_2, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, - .driver_data = SDHCI_QUIRK_SINGLE_POWER_WRITE, + .driver_data = SDHCI_QUIRK_SINGLE_POWER_WRITE | + SDHCI_QUIRK_BROKEN_DMA, }, { @@ -1304,6 +1307,12 @@ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot) else host->flags |= SDHCI_USE_DMA; + if ((chip->quirks & SDHCI_QUIRK_BROKEN_DMA) && + (host->flags & SDHCI_USE_DMA)) { + DBG("Disabling DMA as it is marked broken"); + host->flags &= ~SDHCI_USE_DMA; + } + if (((pdev->class & 0x0000FF) != PCI_SDHCI_IFDMA) && (host->flags & SDHCI_USE_DMA)) { printk(KERN_WARNING "%s: Will use DMA " -- cgit v1.2.3 From d6f8deecefc133cac044f6029bdb349a1cb8753a Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sun, 30 Sep 2007 12:47:05 +0200 Subject: sdhci: remove old dma module params The forcedma and nodma module parameters can be replaced with the quirks parameter, so remove the redundancy. Signed-off-by: Pierre Ossman --- drivers/mmc/host/sdhci.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 2b4a86c55a9..b397121b947 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -25,8 +25,6 @@ #define DBG(f, x...) \ pr_debug(DRIVER_NAME " [%s()]: " f, __func__,## x) -static unsigned int debug_nodma = 0; -static unsigned int debug_forcedma = 0; static unsigned int debug_quirks = 0; #define SDHCI_QUIRK_CLOCK_BEFORE_RESET (1<<0) @@ -1295,12 +1293,7 @@ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot) caps = readl(host->ioaddr + SDHCI_CAPABILITIES); - if (debug_nodma) - DBG("DMA forced off\n"); - else if (debug_forcedma) { - DBG("DMA forced on\n"); - host->flags |= SDHCI_USE_DMA; - } else if (chip->quirks & SDHCI_QUIRK_FORCE_DMA) + if (chip->quirks & SDHCI_QUIRK_FORCE_DMA) host->flags |= SDHCI_USE_DMA; else if (!(caps & SDHCI_CAN_DO_DMA)) DBG("Controller doesn't have DMA capability\n"); @@ -1613,14 +1606,10 @@ static void __exit sdhci_drv_exit(void) module_init(sdhci_drv_init); module_exit(sdhci_drv_exit); -module_param(debug_nodma, uint, 0444); -module_param(debug_forcedma, uint, 0444); module_param(debug_quirks, uint, 0444); MODULE_AUTHOR("Pierre Ossman "); MODULE_DESCRIPTION("Secure Digital Host Controller Interface driver"); MODULE_LICENSE("GPL"); -MODULE_PARM_DESC(debug_nodma, "Forcefully disable DMA transfers. (default 0)"); -MODULE_PARM_DESC(debug_forcedma, "Forcefully enable DMA transfers. (default 0)"); MODULE_PARM_DESC(debug_quirks, "Force certain quirks."); -- cgit v1.2.3 From 3e01e4bcdd56209e70c39293e0c4c355d09364b8 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 3 Oct 2007 15:32:10 -0400 Subject: sdio: fix IRQ diagnostic message If func is actually null we won't get much from sdio_func_id(func). Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman --- drivers/mmc/core/sdio_irq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/mmc/core/sdio_irq.c b/drivers/mmc/core/sdio_irq.c index f78ffeefed5..e7865059e12 100644 --- a/drivers/mmc/core/sdio_irq.c +++ b/drivers/mmc/core/sdio_irq.c @@ -44,7 +44,7 @@ static int process_sdio_pending_irqs(struct mmc_card *card) if (!func) { printk(KERN_WARNING "%s: pending IRQ for " "non-existant function\n", - sdio_func_id(func)); + mmc_card_id(card)); } else if (func->irq_handler) { func->irq_handler(func); count++; -- cgit v1.2.3 From 599473cf15a3fae78cbc3192cfb38ca04d5abc72 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 3 Oct 2007 15:32:57 -0400 Subject: sdio: make the IRQ thread more resilient in the presence of bad states Currently we print a message about some bad states wrt function IRQ handlers but return 0 from process_sdio_pending_irqs() nevertheless. This can lead to an infinite loop as nothing might have cleared the condition for the pending card interrupt from the host controller by the time host->ops->enable_sdio_irq(host, 1) is called. Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman --- drivers/mmc/core/sdio_irq.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/core/sdio_irq.c b/drivers/mmc/core/sdio_irq.c index e7865059e12..3bd3021f5e8 100644 --- a/drivers/mmc/core/sdio_irq.c +++ b/drivers/mmc/core/sdio_irq.c @@ -45,16 +45,22 @@ static int process_sdio_pending_irqs(struct mmc_card *card) printk(KERN_WARNING "%s: pending IRQ for " "non-existant function\n", mmc_card_id(card)); + ret = -EINVAL; } else if (func->irq_handler) { func->irq_handler(func); count++; - } else + } else { printk(KERN_WARNING "%s: pending IRQ with no handler\n", sdio_func_id(func)); + ret = -EINVAL; + } } } - return count; + if (count) + return count; + + return ret; } static int sdio_irq_thread(void *_host) -- cgit v1.2.3 From c783837bc69dd0f329a441c1704f5a02d01d1bd5 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Tue, 9 Oct 2007 17:07:58 -0400 Subject: pxamci: support arbitrary block size The PXA has two transmit FIFOes, each32 byte deep. when one FIFO is full and the other one has been transmitted, they are automatically swapped and DMA is triggered for another 32 byte burst. However, when there is less than 32 bytes left to send, the FIFO swap has to be done manually. This is required for some SDIO transfers which are not required to be multiples of 32 bytes. A DMA completion interrupt is set for each descriptor which length isn't a multiple of 32 in order to force the FIFO swap. While at it, the DMA interrupt handler has been made a bit more resilient against errors. Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman --- drivers/mmc/host/pxamci.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/host/pxamci.c b/drivers/mmc/host/pxamci.c index 91e25683b39..657901eecfc 100644 --- a/drivers/mmc/host/pxamci.c +++ b/drivers/mmc/host/pxamci.c @@ -142,6 +142,10 @@ static void pxamci_setup_data(struct pxamci_host *host, struct mmc_data *data) host->dma_dir); for (i = 0; i < host->dma_len; i++) { + unsigned int length = sg_dma_len(&data->sg[i]); + host->sg_cpu[i].dcmd = dcmd | length; + if (length & 31 && !(data->flags & MMC_DATA_READ)) + host->sg_cpu[i].dcmd |= DCMD_ENDIRQEN; if (data->flags & MMC_DATA_READ) { host->sg_cpu[i].dsadr = host->res->start + MMC_RXFIFO; host->sg_cpu[i].dtadr = sg_dma_address(&data->sg[i]); @@ -149,7 +153,6 @@ static void pxamci_setup_data(struct pxamci_host *host, struct mmc_data *data) host->sg_cpu[i].dsadr = sg_dma_address(&data->sg[i]); host->sg_cpu[i].dtadr = host->res->start + MMC_TXFIFO; } - host->sg_cpu[i].dcmd = dcmd | sg_dma_len(&data->sg[i]); host->sg_cpu[i].ddadr = host->sg_dma + (i + 1) * sizeof(struct pxa_dma_desc); } @@ -414,8 +417,18 @@ static const struct mmc_host_ops pxamci_ops = { static void pxamci_dma_irq(int dma, void *devid) { - printk(KERN_ERR "DMA%d: IRQ???\n", dma); - DCSR(dma) = DCSR_STARTINTR|DCSR_ENDINTR|DCSR_BUSERR; + struct pxamci_host *host = devid; + int dcsr = DCSR(dma); + DCSR(dma) = dcsr & ~DCSR_STOPIRQEN; + + if (dcsr & DCSR_ENDINTR) { + writel(BUF_PART_FULL, host->base + MMC_PRTBUF); + } else { + printk(KERN_ERR "%s: DMA error on channel %d (DCSR=%#x)\n", + mmc_hostname(host->mmc), dma, dcsr); + host->data->error = -EIO; + pxamci_data_done(host, 0); + } } static irqreturn_t pxamci_detect_irq(int irq, void *devid) -- cgit v1.2.3 From 019a5f56ec195aceadada18aaaad0f67294bdaef Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Thu, 11 Oct 2007 01:06:03 -0400 Subject: mmc: don't use weight32() Using weight32() to determine if a value is a power of 2 is a rather heavi weight solution. The classic idiom is (x & (x - 1)) == 0, but the kernel already provide a is_power_of_2 function for it. Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman --- drivers/mmc/host/mmci.c | 3 ++- drivers/mmc/host/tifm_sd.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index 4a72772f4fe..d0eb0a2abf4 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -391,7 +392,7 @@ static void mmci_request(struct mmc_host *mmc, struct mmc_request *mrq) WARN_ON(host->mrq != NULL); - if (mrq->data && (hweight32(mrq->data->blksz) > 1)) { + if (mrq->data && !is_power_of_2(mrq->data->blksz)) { printk(KERN_ERR "%s: Unsupported block size (%d bytes)\n", mmc_hostname(mmc), mrq->data->blksz); mrq->cmd->error = -EINVAL; diff --git a/drivers/mmc/host/tifm_sd.c b/drivers/mmc/host/tifm_sd.c index 951392d2ce3..9b904795eb7 100644 --- a/drivers/mmc/host/tifm_sd.c +++ b/drivers/mmc/host/tifm_sd.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #define DRIVER_NAME "tifm_sd" @@ -637,7 +638,7 @@ static void tifm_sd_request(struct mmc_host *mmc, struct mmc_request *mrq) goto err_out; } - if (mrq->data && (hweight32(mrq->data->blksz) > 1)) { + if (mrq->data && !is_power_of_2(mrq->data->blksz)) { printk(KERN_ERR "%s: Unsupported block size (%d bytes)\n", sock->dev.bus_id, mrq->data->blksz); mrq->cmd->error = -EINVAL; -- cgit v1.2.3