From bce40a36de574376f41f1ff3c4d212a7da2a3c90 Mon Sep 17 00:00:00 2001 From: Philip Langdale Date: Sat, 21 Oct 2006 12:35:02 +0200 Subject: [PATCH] mmc: Add support for mmc v4 high speed mode This adds support for the high-speed modes defined by mmc v4 (assuming the host controller is up to it). On a TI sdhci controller, it improves read speed from 1.3MBps to 2.3MBps. The TI controller can only go up to 24MHz, but everything helps. Another person has taken this basic patch and used it on a Nokia 770 to get a bigger boost because that controller can run at 48MHZ. Signed-off-by: Philip Langdale Signed-off-by: Pierre Ossman --- drivers/mmc/mmc.c | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 119 insertions(+), 2 deletions(-) (limited to 'drivers/mmc/mmc.c') diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index 766bc54406e..2d5b93000de 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -4,6 +4,7 @@ * Copyright (C) 2003-2004 Russell King, All Rights Reserved. * SD support Copyright (C) 2004 Ian Molton, All Rights Reserved. * SD support Copyright (C) 2005 Pierre Ossman, All Rights Reserved. + * MMCv4 support Copyright (C) 2006 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 version 2 as @@ -953,6 +954,114 @@ static void mmc_read_csds(struct mmc_host *host) } } +static void mmc_process_ext_csds(struct mmc_host *host) +{ + int err; + struct mmc_card *card; + + struct mmc_request mrq; + struct mmc_command cmd; + struct mmc_data data; + + struct scatterlist sg; + + /* + * As the ext_csd is so large and mostly unused, we don't store the + * raw block in mmc_card. + */ + u8 *ext_csd; + ext_csd = kmalloc(512, GFP_KERNEL); + if (!ext_csd) { + printk("%s: could not allocate a buffer to receive the ext_csd." + "mmc v4 cards will be treated as v3.\n", + mmc_hostname(host)); + return; + } + + list_for_each_entry(card, &host->cards, node) { + if (card->state & (MMC_STATE_DEAD|MMC_STATE_PRESENT)) + continue; + if (mmc_card_sd(card)) + continue; + if (card->csd.mmca_vsn < CSD_SPEC_VER_4) + continue; + + err = mmc_select_card(host, card); + if (err != MMC_ERR_NONE) { + mmc_card_set_dead(card); + continue; + } + + memset(&cmd, 0, sizeof(struct mmc_command)); + + cmd.opcode = MMC_SEND_EXT_CSD; + cmd.arg = 0; + cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + + memset(&data, 0, sizeof(struct mmc_data)); + + mmc_set_data_timeout(&data, card, 0); + + data.blksz = 512; + data.blocks = 1; + data.flags = MMC_DATA_READ; + data.sg = &sg; + data.sg_len = 1; + + memset(&mrq, 0, sizeof(struct mmc_request)); + + mrq.cmd = &cmd; + mrq.data = &data; + + sg_init_one(&sg, ext_csd, 512); + + mmc_wait_for_req(host, &mrq); + + if (cmd.error != MMC_ERR_NONE || data.error != MMC_ERR_NONE) { + mmc_card_set_dead(card); + continue; + } + + switch (ext_csd[EXT_CSD_CARD_TYPE]) { + case EXT_CSD_CARD_TYPE_52 | EXT_CSD_CARD_TYPE_26: + card->ext_csd.hs_max_dtr = 52000000; + break; + case EXT_CSD_CARD_TYPE_26: + card->ext_csd.hs_max_dtr = 26000000; + break; + default: + /* MMC v4 spec says this cannot happen */ + printk("%s: card is mmc v4 but doesn't support " + "any high-speed modes.\n", + mmc_hostname(card->host)); + mmc_card_set_bad(card); + continue; + } + + /* Activate highspeed support. */ + cmd.opcode = MMC_SWITCH; + cmd.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) | + (EXT_CSD_HS_TIMING << 16) | + (1 << 8) | + EXT_CSD_CMD_SET_NORMAL; + cmd.flags = MMC_RSP_R1B | MMC_CMD_AC; + + err = mmc_wait_for_cmd(host, &cmd, CMD_RETRIES); + if (err != MMC_ERR_NONE) { + printk("%s: failed to switch card to mmc v4 " + "high-speed mode.\n", + mmc_hostname(card->host)); + continue; + } + + mmc_card_set_highspeed(card); + } + + kfree(ext_csd); + + mmc_deselect_cards(host); +} + static void mmc_read_scrs(struct mmc_host *host) { int err; @@ -1031,8 +1140,14 @@ static unsigned int mmc_calculate_clock(struct mmc_host *host) unsigned int max_dtr = host->f_max; list_for_each_entry(card, &host->cards, node) - if (!mmc_card_dead(card) && max_dtr > card->csd.max_dtr) - max_dtr = card->csd.max_dtr; + if (!mmc_card_dead(card)) { + if (mmc_card_highspeed(card)) { + if (max_dtr > card->ext_csd.hs_max_dtr) + max_dtr = card->ext_csd.hs_max_dtr; + } else if (max_dtr > card->csd.max_dtr) { + max_dtr = card->csd.max_dtr; + } + } pr_debug("%s: selected %d.%03dMHz transfer rate\n", mmc_hostname(host), @@ -1152,6 +1267,8 @@ static void mmc_setup(struct mmc_host *host) if (host->mode == MMC_MODE_SD) mmc_read_scrs(host); + else + mmc_process_ext_csds(host); } -- cgit v1.2.3 From e45a1bd20fa5b920901879e85cdf5eda21f78d7c Mon Sep 17 00:00:00 2001 From: Philip Langdale Date: Sun, 29 Oct 2006 10:14:19 +0100 Subject: mmc: Add support for mmc v4 wide-bus modes This change adds support for the mmc4 4-bit wide-bus mode. The mmc4 spec defines 8-bit and 4-bit transfer modes. As we do not support any 8-bit hardware, this patch only adds support for the 4-bit mode, but it can easily be built upon when the time comes. The 4-bit mode is electrically compatible with SD's 4-bit mode but the procedure for turning it on is different. This patch implements only the essential parts of the procedure as defined by the spec. Two additional steps are recommended but not compulsory. I am documenting them here so that there's a record. 1) A bus-test mechanism is implemented using dedicated mmc commands which allow for testing the functionality of the data bus at the electrical level. This is pretty paranoid and they way the commands work is not compatible with the mmc subsystem (they don't set valid CRC values). 2) MMC v4 cards can indicate they would like to draw more than the default amount of current in wide-bus modes. We currently will never switch the card into a higher draw mode. Supposedly, allowing the card to draw more current will let it perform better, but the specs seem to indicate that the card will function correctly without the mode change. Empirical testing supports this interpretation. Signed-off-by: Philip Langdale Signed-off-by: Pierre Ossman --- drivers/mmc/mmc.c | 51 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 14 deletions(-) (limited to 'drivers/mmc/mmc.c') diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index 2d5b93000de..1593a6a632c 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -397,23 +397,23 @@ static int mmc_select_card(struct mmc_host *host, struct mmc_card *card) return err; /* - * Default bus width is 1 bit. - */ - host->ios.bus_width = MMC_BUS_WIDTH_1; - - /* - * We can only change the bus width of the selected - * card so therefore we have to put the handling + * We can only change the bus width of SD cards when + * they are selected so we have to put the handling * here. + * + * The card is in 1 bit mode by default so + * we only need to change if it supports the + * wider version. */ - if (host->caps & MMC_CAP_4_BIT_DATA) { + if (mmc_card_sd(card) && + (card->scr.bus_widths & SD_SCR_BUS_WIDTH_4)) { + /* - * The card is in 1 bit mode by default so - * we only need to change if it supports the - * wider version. - */ - if (mmc_card_sd(card) && - (card->scr.bus_widths & SD_SCR_BUS_WIDTH_4)) { + * Default bus width is 1 bit. + */ + host->ios.bus_width = MMC_BUS_WIDTH_1; + + if (host->caps & MMC_CAP_4_BIT_DATA) { struct mmc_command cmd; cmd.opcode = SD_APP_SET_BUS_WIDTH; cmd.arg = SD_BUS_WIDTH_4; @@ -1055,6 +1055,29 @@ static void mmc_process_ext_csds(struct mmc_host *host) } mmc_card_set_highspeed(card); + + /* Check for host support for wide-bus modes. */ + if (!(host->caps & MMC_CAP_4_BIT_DATA)) { + continue; + } + + /* Activate 4-bit support. */ + cmd.opcode = MMC_SWITCH; + cmd.arg = (MMC_SWITCH_MODE_WRITE_BYTE << 24) | + (EXT_CSD_BUS_WIDTH << 16) | + (EXT_CSD_BUS_WIDTH_4 << 8) | + EXT_CSD_CMD_SET_NORMAL; + cmd.flags = MMC_RSP_R1B | MMC_CMD_AC; + + err = mmc_wait_for_cmd(host, &cmd, CMD_RETRIES); + if (err != MMC_ERR_NONE) { + printk("%s: failed to switch card to " + "mmc v4 4-bit bus mode.\n", + mmc_hostname(card->host)); + continue; + } + + host->ios.bus_width = MMC_BUS_WIDTH_4; } kfree(ext_csd); -- cgit v1.2.3 From 73778120c4088a0a7b59c4c378904f7a230b4820 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Sun, 22 Oct 2006 22:13:10 +0200 Subject: mmc: Fix mmc_delay() function Several fixes for mmc_delay(): * Repair if-clause that was supposed to detect sub-hz delays. * Change yield() to cond_resched() as yield() no longer has the semantics we desire. * mmc_delay() is used to guarantee protocol delays, so we cannot return early (i.e. use _interruptable). Based on patch by Amol Lad. Signed-off-by: Pierre Ossman --- drivers/mmc/mmc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/mmc/mmc.c') diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index 1593a6a632c..82b7643c165 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -454,11 +454,11 @@ static void mmc_deselect_cards(struct mmc_host *host) static inline void mmc_delay(unsigned int ms) { - if (ms < HZ / 1000) { - yield(); + if (ms < 1000 / HZ) { + cond_resched(); mdelay(ms); } else { - msleep_interruptible (ms); + msleep(ms); } } -- cgit v1.2.3 From 7ccd266e676a3f0c6f8f897f58b684cac3dd1650 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Wed, 8 Nov 2006 23:03:10 +0100 Subject: mmc: Support for high speed SD cards Modern SD cards support a clock speed of 50 MHz. Make sure we test for this capability and do the song and dance required to activate it. Activating high speed support actually modifies the TRAN_SPEED field of the CSD. But as the spec says that the cards must report 50 MHz, we might as well skip re-reading the CSD. Signed-off-by: Pierre Ossman --- drivers/mmc/mmc.c | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 118 insertions(+), 4 deletions(-) (limited to 'drivers/mmc/mmc.c') diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c index 82b7643c165..9d190022a49 100644 --- a/drivers/mmc/mmc.c +++ b/drivers/mmc/mmc.c @@ -1157,6 +1157,116 @@ static void mmc_read_scrs(struct mmc_host *host) mmc_deselect_cards(host); } +static void mmc_read_switch_caps(struct mmc_host *host) +{ + int err; + struct mmc_card *card; + struct mmc_request mrq; + struct mmc_command cmd; + struct mmc_data data; + unsigned char *status; + struct scatterlist sg; + + status = kmalloc(64, GFP_KERNEL); + if (!status) { + printk(KERN_WARNING "%s: Unable to allocate buffer for " + "reading switch capabilities.\n", + mmc_hostname(host)); + return; + } + + list_for_each_entry(card, &host->cards, node) { + if (card->state & (MMC_STATE_DEAD|MMC_STATE_PRESENT)) + continue; + if (!mmc_card_sd(card)) + continue; + if (card->scr.sda_vsn < SCR_SPEC_VER_1) + continue; + + err = mmc_select_card(host, card); + if (err != MMC_ERR_NONE) { + mmc_card_set_dead(card); + continue; + } + + memset(&cmd, 0, sizeof(struct mmc_command)); + + cmd.opcode = SD_SWITCH; + cmd.arg = 0x00FFFFF1; + cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + + memset(&data, 0, sizeof(struct mmc_data)); + + mmc_set_data_timeout(&data, card, 0); + + data.blksz = 64; + data.blocks = 1; + data.flags = MMC_DATA_READ; + data.sg = &sg; + data.sg_len = 1; + + memset(&mrq, 0, sizeof(struct mmc_request)); + + mrq.cmd = &cmd; + mrq.data = &data; + + sg_init_one(&sg, status, 64); + + mmc_wait_for_req(host, &mrq); + + if (cmd.error != MMC_ERR_NONE || data.error != MMC_ERR_NONE) { + mmc_card_set_dead(card); + continue; + } + + if (status[13] & 0x02) + card->sw_caps.hs_max_dtr = 50000000; + + memset(&cmd, 0, sizeof(struct mmc_command)); + + cmd.opcode = SD_SWITCH; + cmd.arg = 0x80FFFFF1; + cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + + memset(&data, 0, sizeof(struct mmc_data)); + + mmc_set_data_timeout(&data, card, 0); + + data.blksz = 64; + data.blocks = 1; + data.flags = MMC_DATA_READ; + data.sg = &sg; + data.sg_len = 1; + + memset(&mrq, 0, sizeof(struct mmc_request)); + + mrq.cmd = &cmd; + mrq.data = &data; + + sg_init_one(&sg, status, 64); + + mmc_wait_for_req(host, &mrq); + + if (cmd.error != MMC_ERR_NONE || data.error != MMC_ERR_NONE) { + mmc_card_set_dead(card); + continue; + } + + if ((status[16] & 0xF) != 1) { + printk(KERN_WARNING "%s: Problem switching card " + "into high-speed mode!\n", + mmc_hostname(host)); + continue; + } + + mmc_card_set_highspeed(card); + } + + kfree(status); + + mmc_deselect_cards(host); +} + static unsigned int mmc_calculate_clock(struct mmc_host *host) { struct mmc_card *card; @@ -1164,9 +1274,12 @@ static unsigned int mmc_calculate_clock(struct mmc_host *host) list_for_each_entry(card, &host->cards, node) if (!mmc_card_dead(card)) { - if (mmc_card_highspeed(card)) { + if (mmc_card_highspeed(card) && mmc_card_sd(card)) { + if (max_dtr > card->sw_caps.hs_max_dtr) + max_dtr = card->sw_caps.hs_max_dtr; + } else if (mmc_card_highspeed(card) && !mmc_card_sd(card)) { if (max_dtr > card->ext_csd.hs_max_dtr) - max_dtr = card->ext_csd.hs_max_dtr; + max_dtr = card->ext_csd.hs_max_dtr; } else if (max_dtr > card->csd.max_dtr) { max_dtr = card->csd.max_dtr; } @@ -1288,9 +1401,10 @@ static void mmc_setup(struct mmc_host *host) mmc_read_csds(host); - if (host->mode == MMC_MODE_SD) + if (host->mode == MMC_MODE_SD) { mmc_read_scrs(host); - else + mmc_read_switch_caps(host); + } else mmc_process_ext_csds(host); } -- cgit v1.2.3