From 2c96a293bbd6b34698c6710ea8607049956247c4 Mon Sep 17 00:00:00 2001 From: Ludovic Desroches Date: Thu, 11 Aug 2011 15:25:41 +0000 Subject: mmc: atmel-mci: change namespace Homogenize namespace to atmci. Signed-off-by: Ludovic Desroches Signed-off-by: Nicolas Ferre Signed-off-by: Chris Ball --- include/linux/atmel-mci.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/atmel-mci.h b/include/linux/atmel-mci.h index 3e09b345f4d6..4c7a4b2104bf 100644 --- a/include/linux/atmel-mci.h +++ b/include/linux/atmel-mci.h @@ -1,7 +1,7 @@ #ifndef __LINUX_ATMEL_MCI_H #define __LINUX_ATMEL_MCI_H -#define ATMEL_MCI_MAX_NR_SLOTS 2 +#define ATMCI_MAX_NR_SLOTS 2 /** * struct mci_slot_pdata - board-specific per-slot configuration @@ -33,7 +33,7 @@ struct mci_slot_pdata { */ struct mci_platform_data { struct mci_dma_data *dma_slave; - struct mci_slot_pdata slot[ATMEL_MCI_MAX_NR_SLOTS]; + struct mci_slot_pdata slot[ATMCI_MAX_NR_SLOTS]; }; #endif /* __LINUX_ATMEL_MCI_H */ -- cgit v1.2.3 From 1ebbe3d31fd96865b4d4874f3c74fef0e386fb79 Mon Sep 17 00:00:00 2001 From: Ludovic Desroches Date: Thu, 11 Aug 2011 15:25:46 +0000 Subject: mmc: atmel-mci: use ATMEL_PDC_SCND_BUF_OFF instead of a literal value Signed-off-by: Ludovic Desroches Signed-off-by: Nicolas Ferre Signed-off-by: Chris Ball --- drivers/mmc/host/atmel-mci.c | 4 ++-- include/linux/atmel_pdc.h | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/drivers/mmc/host/atmel-mci.c b/drivers/mmc/host/atmel-mci.c index caae967dcafe..c0a0659261aa 100644 --- a/drivers/mmc/host/atmel-mci.c +++ b/drivers/mmc/host/atmel-mci.c @@ -603,8 +603,8 @@ static void atmci_pdc_set_single_buf(struct atmel_mci *host, } if (buf_nb == PDC_SECOND_BUF) { - pointer_reg += 0x10; - counter_reg += 0x10; + pointer_reg += ATMEL_PDC_SCND_BUF_OFF; + counter_reg += ATMEL_PDC_SCND_BUF_OFF; } atmci_writel(host, pointer_reg, sg_dma_address(host->sg)); diff --git a/include/linux/atmel_pdc.h b/include/linux/atmel_pdc.h index 5058a31d2ce8..63499ce806ea 100644 --- a/include/linux/atmel_pdc.h +++ b/include/linux/atmel_pdc.h @@ -33,4 +33,6 @@ #define ATMEL_PDC_PTSR 0x124 /* Transfer Status Register */ +#define ATMEL_PDC_SCND_BUF_OFF 0x10 /* Offset between first and second buffer registers */ + #endif -- cgit v1.2.3 From 1b676f70c108cda90cf9d114d16c677584400efc Mon Sep 17 00:00:00 2001 From: Per Forlin Date: Fri, 19 Aug 2011 14:52:37 +0200 Subject: mmc: core: add random fault injection This adds support to inject data errors after a completed host transfer. The mmc core will return error even though the host transfer is successful. This simple fault injection proved to be very useful to test the non-blocking error handling in the mmc_blk_issue_rw_rq(). Random faults can also test how the host driver handles pre_req() and post_req() in case of errors. Signed-off-by: Per Forlin Acked-by: Akinobu Mita Reviewed-by: Linus Walleij Signed-off-by: Chris Ball --- drivers/mmc/core/core.c | 41 +++++++++++++++++++++++++++++++++++++++++ drivers/mmc/core/debugfs.c | 25 +++++++++++++++++++++++++ include/linux/mmc/host.h | 5 +++++ lib/Kconfig.debug | 11 +++++++++++ 4 files changed, 82 insertions(+) (limited to 'include') diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index b27b94078c21..eb3069dfea8e 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include #include @@ -83,6 +85,43 @@ static void mmc_flush_scheduled_work(void) flush_workqueue(workqueue); } +#ifdef CONFIG_FAIL_MMC_REQUEST + +/* + * Internal function. Inject random data errors. + * If mmc_data is NULL no errors are injected. + */ +static void mmc_should_fail_request(struct mmc_host *host, + struct mmc_request *mrq) +{ + struct mmc_command *cmd = mrq->cmd; + struct mmc_data *data = mrq->data; + static const int data_errors[] = { + -ETIMEDOUT, + -EILSEQ, + -EIO, + }; + + if (!data) + return; + + if (cmd->error || data->error || + !should_fail(&host->fail_mmc_request, data->blksz * data->blocks)) + return; + + data->error = data_errors[random32() % ARRAY_SIZE(data_errors)]; + data->bytes_xfered = (random32() % (data->bytes_xfered >> 9)) << 9; +} + +#else /* CONFIG_FAIL_MMC_REQUEST */ + +static inline void mmc_should_fail_request(struct mmc_host *host, + struct mmc_request *mrq) +{ +} + +#endif /* CONFIG_FAIL_MMC_REQUEST */ + /** * mmc_request_done - finish processing an MMC request * @host: MMC host which completed request @@ -109,6 +148,8 @@ void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq) cmd->error = 0; host->ops->request(host, mrq); } else { + mmc_should_fail_request(host, mrq); + led_trigger_event(host->led, LED_OFF); pr_debug("%s: req done (CMD%u): %d: %08x %08x %08x %08x\n", diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c index 998797ed67a6..5acd707699c9 100644 --- a/drivers/mmc/core/debugfs.c +++ b/drivers/mmc/core/debugfs.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -158,6 +159,23 @@ static int mmc_clock_opt_set(void *data, u64 val) return 0; } +#ifdef CONFIG_FAIL_MMC_REQUEST + +static DECLARE_FAULT_ATTR(fail_mmc_request); + +#ifdef KERNEL +/* + * Internal function. Pass the boot param fail_mmc_request to + * the setup fault injection attributes routine. + */ +static int __init setup_fail_mmc_request(char *str) +{ + return setup_fault_attr(&fail_mmc_request, str); +} +__setup("fail_mmc_request=", setup_fail_mmc_request); +#endif /* KERNEL */ +#endif /* CONFIG_FAIL_MMC_REQUEST */ + DEFINE_SIMPLE_ATTRIBUTE(mmc_clock_fops, mmc_clock_opt_get, mmc_clock_opt_set, "%llu\n"); @@ -187,6 +205,13 @@ void mmc_add_host_debugfs(struct mmc_host *host) if (!debugfs_create_u32("clk_delay", (S_IRUSR | S_IWUSR), root, &host->clk_delay)) goto err_node; +#endif +#ifdef CONFIG_FAIL_MMC_REQUEST + host->fail_mmc_request = fail_mmc_request; + if (IS_ERR(fault_create_debugfs_attr("fail_mmc_request", + root, + &host->fail_mmc_request))) + goto err_node; #endif return; diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 1d09562ccf73..4c4bddf5ef61 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -302,6 +303,10 @@ struct mmc_host { struct mmc_async_req *areq; /* active async req */ +#ifdef CONFIG_FAIL_MMC_REQUEST + struct fault_attr fail_mmc_request; +#endif + unsigned long private[0] ____cacheline_aligned; }; diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index c0cb9c4bc46d..1c7dbbf9e449 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -1070,6 +1070,17 @@ config FAIL_IO_TIMEOUT Only works with drivers that use the generic timeout handling, for others it wont do anything. +config FAIL_MMC_REQUEST + bool "Fault-injection capability for MMC IO" + select DEBUG_FS + depends on FAULT_INJECTION && MMC + help + Provide fault-injection capability for MMC IO. + This will make the mmc core return data errors. This is + useful to test the error handling in the mmc block device + and to test how the mmc host driver handles retries from + the block device. + config FAULT_INJECTION_DEBUG_FS bool "Debugfs entries for fault-injection capabilities" depends on FAULT_INJECTION && SYSFS && DEBUG_FS -- cgit v1.2.3 From d5098cb63b3f13da2a2b230b3566ac7b5dfa4f28 Mon Sep 17 00:00:00 2001 From: Simon Horman Date: Fri, 26 Aug 2011 10:42:39 +0200 Subject: mmc: sdhi: Allow named IRQs to use specific handlers Allow named IRQs to use corresponding specific handlers. If named IRQs are used, at least an "sdcard" IRQ has to be specified by the platform. If names are not used, an arbitrary number of IRQs can be provided by the platform, in which case the generic ISR will be used for each of them. Cc: Guennadi Liakhovetski Acked-by: Magnus Damm Signed-off-by: Simon Horman [g.liakhovetski@gmx.de: style and typo corrections, platform data check] Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mobile_sdhi.c | 98 ++++++++++++++++++++++++++++---------- include/linux/mmc/sh_mobile_sdhi.h | 4 ++ 2 files changed, 77 insertions(+), 25 deletions(-) (limited to 'include') diff --git a/drivers/mmc/host/sh_mobile_sdhi.c b/drivers/mmc/host/sh_mobile_sdhi.c index 0c4a672f5db6..75bffc4768fe 100644 --- a/drivers/mmc/host/sh_mobile_sdhi.c +++ b/drivers/mmc/host/sh_mobile_sdhi.c @@ -96,7 +96,8 @@ static int __devinit sh_mobile_sdhi_probe(struct platform_device *pdev) struct sh_mobile_sdhi_info *p = pdev->dev.platform_data; struct tmio_mmc_host *host; char clk_name[8]; - int i, irq, ret; + int irq, ret, i = 0; + bool multiplexed_isr = true; priv = kzalloc(sizeof(struct sh_mobile_sdhi), GFP_KERNEL); if (priv == NULL) { @@ -153,27 +154,60 @@ static int __devinit sh_mobile_sdhi_probe(struct platform_device *pdev) if (ret < 0) goto eprobe; - for (i = 0; i < 3; i++) { - irq = platform_get_irq(pdev, i); - if (irq < 0) { - if (i) { - continue; - } else { - ret = irq; - goto eirq; - } - } - ret = request_irq(irq, tmio_mmc_irq, 0, + /* + * Allow one or more specific (named) ISRs or + * one or more multiplexed (un-named) ISRs. + */ + + irq = platform_get_irq_byname(pdev, SH_MOBILE_SDHI_IRQ_CARD_DETECT); + if (irq >= 0) { + multiplexed_isr = false; + ret = request_irq(irq, tmio_mmc_card_detect_irq, 0, + dev_name(&pdev->dev), host); + if (ret) + goto eirq_card_detect; + } + + irq = platform_get_irq_byname(pdev, SH_MOBILE_SDHI_IRQ_SDIO); + if (irq >= 0) { + multiplexed_isr = false; + ret = request_irq(irq, tmio_mmc_sdio_irq, 0, + dev_name(&pdev->dev), host); + if (ret) + goto eirq_sdio; + } + + irq = platform_get_irq_byname(pdev, SH_MOBILE_SDHI_IRQ_SDCARD); + if (irq >= 0) { + multiplexed_isr = false; + ret = request_irq(irq, tmio_mmc_sdcard_irq, 0, dev_name(&pdev->dev), host); - if (ret) { - while (i--) { - irq = platform_get_irq(pdev, i); - if (irq >= 0) - free_irq(irq, host); - } - goto eirq; + if (ret) + goto eirq_sdcard; + } else if (!multiplexed_isr) { + dev_err(&pdev->dev, + "Principal SD-card IRQ is missing among named interrupts\n"); + ret = irq; + goto eirq_sdcard; + } + + if (multiplexed_isr) { + while (1) { + irq = platform_get_irq(pdev, i); + if (irq < 0) + break; + i++; + ret = request_irq(irq, tmio_mmc_irq, 0, + dev_name(&pdev->dev), host); + if (ret) + goto eirq_multiplexed; } + + /* There must be at least one IRQ source */ + if (!i) + goto eirq_multiplexed; } + dev_info(&pdev->dev, "%s base at 0x%08lx clock rate %u MHz\n", mmc_hostname(host->mmc), (unsigned long) (platform_get_resource(pdev,IORESOURCE_MEM, 0)->start), @@ -181,7 +215,20 @@ static int __devinit sh_mobile_sdhi_probe(struct platform_device *pdev) return ret; -eirq: +eirq_multiplexed: + while (i--) { + irq = platform_get_irq(pdev, i); + free_irq(irq, host); + } +eirq_sdcard: + irq = platform_get_irq_byname(pdev, SH_MOBILE_SDHI_IRQ_SDIO); + if (irq >= 0) + free_irq(irq, host); +eirq_sdio: + irq = platform_get_irq_byname(pdev, SH_MOBILE_SDHI_IRQ_CARD_DETECT); + if (irq >= 0) + free_irq(irq, host); +eirq_card_detect: tmio_mmc_host_remove(host); eprobe: clk_disable(priv->clk); @@ -197,16 +244,17 @@ static int sh_mobile_sdhi_remove(struct platform_device *pdev) struct tmio_mmc_host *host = mmc_priv(mmc); struct sh_mobile_sdhi *priv = container_of(host->pdata, struct sh_mobile_sdhi, mmc_data); struct sh_mobile_sdhi_info *p = pdev->dev.platform_data; - int i, irq; + int i = 0, irq; p->pdata = NULL; tmio_mmc_host_remove(host); - for (i = 0; i < 3; i++) { - irq = platform_get_irq(pdev, i); - if (irq >= 0) - free_irq(irq, host); + while (1) { + irq = platform_get_irq(pdev, i++); + if (irq < 0) + break; + free_irq(irq, host); } clk_disable(priv->clk); diff --git a/include/linux/mmc/sh_mobile_sdhi.h b/include/linux/mmc/sh_mobile_sdhi.h index bd50b365167f..71b805451bd8 100644 --- a/include/linux/mmc/sh_mobile_sdhi.h +++ b/include/linux/mmc/sh_mobile_sdhi.h @@ -6,6 +6,10 @@ struct platform_device; struct tmio_mmc_data; +#define SH_MOBILE_SDHI_IRQ_CARD_DETECT "card_detect" +#define SH_MOBILE_SDHI_IRQ_SDCARD "sdcard" +#define SH_MOBILE_SDHI_IRQ_SDIO "sdio" + struct sh_mobile_sdhi_info { int dma_slave_tx; int dma_slave_rx; -- cgit v1.2.3 From 9a0da648ff3a5020406ac7784eb3b519014f66f6 Mon Sep 17 00:00:00 2001 From: Stefan Nilsson XK Date: Thu, 15 Sep 2011 17:43:04 +0200 Subject: mmc: sdio: Workaround for dev with broken CMD53 Adds a quirk which can be turned on for SDIO devices that do not support 512 byte requests in byte mode during CMD53. These requests will always be sent in block mode instead. This patch also enables this quirk for ST-Ericsson CW1200 WLAN device. Signed-off-by: Stefan Nilsson XK Signed-off-by: Ulf HANSSON Acked-by: Linus Walleij Signed-off-by: Chris Ball --- drivers/mmc/core/quirks.c | 11 +++++++++++ drivers/mmc/core/sdio_ops.c | 7 +++++-- include/linux/mmc/card.h | 7 +++++++ 3 files changed, 23 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/drivers/mmc/core/quirks.c b/drivers/mmc/core/quirks.c index 3a596217029e..6c3cf98a62eb 100644 --- a/drivers/mmc/core/quirks.c +++ b/drivers/mmc/core/quirks.c @@ -21,6 +21,14 @@ #define SDIO_DEVICE_ID_TI_WL1271 0x4076 #endif +#ifndef SDIO_VENDOR_ID_STE +#define SDIO_VENDOR_ID_STE 0x0020 +#endif + +#ifndef SDIO_DEVICE_ID_STE_CW1200 +#define SDIO_DEVICE_ID_STE_CW1200 0x2280 +#endif + /* * This hook just adds a quirk for all sdio devices */ @@ -46,6 +54,9 @@ static const struct mmc_fixup mmc_fixup_methods[] = { SDIO_FIXUP(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271, add_quirk, MMC_QUIRK_DISABLE_CD), + SDIO_FIXUP(SDIO_VENDOR_ID_STE, SDIO_DEVICE_ID_STE_CW1200, + add_quirk, MMC_QUIRK_BROKEN_BYTE_MODE_512), + END_FIXUP }; diff --git a/drivers/mmc/core/sdio_ops.c b/drivers/mmc/core/sdio_ops.c index 4addbe987bc9..b0517cc06200 100644 --- a/drivers/mmc/core/sdio_ops.c +++ b/drivers/mmc/core/sdio_ops.c @@ -144,8 +144,11 @@ int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn, cmd.arg |= fn << 28; cmd.arg |= incr_addr ? 0x04000000 : 0x00000000; cmd.arg |= addr << 9; - if (blocks == 1 && blksz <= 512) - cmd.arg |= (blksz == 512) ? 0 : blksz; /* byte mode */ + if (blocks == 1 && blksz < 512) + cmd.arg |= blksz; /* byte mode */ + else if (blocks == 1 && blksz == 512 && + !(mmc_card_broken_byte_mode_512(card))) + cmd.arg |= 0; /* byte mode, 0==512 */ else cmd.arg |= 0x08000000 | blocks; /* block mode */ cmd.flags = MMC_RSP_SPI_R5 | MMC_RSP_R5 | MMC_CMD_ADTC; diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index b460fc2af8a1..6dfb293326e2 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -188,6 +188,8 @@ struct mmc_card { #define MMC_QUIRK_DISABLE_CD (1<<5) /* disconnect CD/DAT[3] resistor */ #define MMC_QUIRK_INAND_CMD38 (1<<6) /* iNAND devices have broken CMD38 */ #define MMC_QUIRK_BLK_NO_CMD23 (1<<7) /* Avoid CMD23 for regular multiblock */ +#define MMC_QUIRK_BROKEN_BYTE_MODE_512 (1<<8) /* Avoid sending 512 bytes in */ + /* byte mode */ unsigned int erase_size; /* erase size in sectors */ unsigned int erase_shift; /* if erase unit is power 2 */ @@ -377,6 +379,11 @@ static inline int mmc_card_nonstd_func_interface(const struct mmc_card *c) return c->quirks & MMC_QUIRK_NONSTD_FUNC_IF; } +static inline int mmc_card_broken_byte_mode_512(const struct mmc_card *c) +{ + return c->quirks & MMC_QUIRK_BROKEN_BYTE_MODE_512; +} + #define mmc_card_name(c) ((c)->cid.prod_name) #define mmc_card_id(c) (dev_name(&(c)->dev)) -- cgit v1.2.3 From 7c8a2829c22a270acadc6aa3a937e2e7956b19f5 Mon Sep 17 00:00:00 2001 From: Per Forlin Date: Mon, 29 Aug 2011 15:35:58 +0200 Subject: mmc: core: clarify how to use post_req in case of errors The err condition in post_req() is set to undo a call made to pre_req() that hasn't been started yet. The err condition is not set if an MMC request returns an error. Signed-off-by: Per Forlin Acked-by: Linus Walleij Signed-off-by: Chris Ball --- drivers/mmc/core/core.c | 6 ++++++ include/linux/mmc/host.h | 3 +++ 2 files changed, 9 insertions(+) (limited to 'include') diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 45ea968e7dd1..0b4c2ed22bce 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -320,8 +320,14 @@ struct mmc_async_req *mmc_start_req(struct mmc_host *host, mmc_wait_for_req_done(host, host->areq->mrq); err = host->areq->err_check(host->card, host->areq); if (err) { + /* post process the completed failed request */ mmc_post_req(host, host->areq->mrq, 0); if (areq) + /* + * Cancel the new prepared request, because + * it can't run until the failed + * request has been properly handled. + */ mmc_post_req(host, areq->mrq, -EINVAL); host->areq = NULL; diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 4c4bddf5ef61..340cc0c9409f 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -109,6 +109,9 @@ struct mmc_host_ops { * It is optional for the host to implement pre_req and post_req in * order to support double buffering of requests (prepare one * request while another request is active). + * pre_req() must always be followed by a post_req(). + * To undo a call made to pre_req(), call post_req() with + * a nonzero err condition. */ void (*post_req)(struct mmc_host *host, struct mmc_request *req, int err); -- cgit v1.2.3 From b2499518b5ad7e28bb3ed348fd3f370eeb1e36c0 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 29 Aug 2011 16:42:11 +0300 Subject: mmc: core: add eMMC hardware reset support eMMC's may have a hardware reset line. This patch provides a host controller operation to implement hardware reset and a function to reset and reinitialize the card. Also, for MMC, the reset is always performed before initialization. The host must set the new host capability MMC_CAP_HW_RESET to enable hardware reset. Signed-off-by: Adrian Hunter Signed-off-by: Chris Ball --- drivers/mmc/core/core.c | 94 ++++++++++++++++++++++++++++++++++++++++++++++++ drivers/mmc/core/mmc.c | 4 ++- include/linux/mmc/card.h | 1 + include/linux/mmc/core.h | 3 ++ include/linux/mmc/host.h | 2 ++ include/linux/mmc/mmc.h | 4 +++ 6 files changed, 107 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 0b4c2ed22bce..da6bd95fa4bb 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1776,6 +1776,94 @@ int mmc_set_blocklen(struct mmc_card *card, unsigned int blocklen) } EXPORT_SYMBOL(mmc_set_blocklen); +static void mmc_hw_reset_for_init(struct mmc_host *host) +{ + if (!(host->caps & MMC_CAP_HW_RESET) || !host->ops->hw_reset) + return; + mmc_host_clk_hold(host); + host->ops->hw_reset(host); + mmc_host_clk_release(host); +} + +int mmc_can_reset(struct mmc_card *card) +{ + u8 rst_n_function; + + if (!mmc_card_mmc(card)) + return 0; + rst_n_function = card->ext_csd.rst_n_function; + if ((rst_n_function & EXT_CSD_RST_N_EN_MASK) != EXT_CSD_RST_N_ENABLED) + return 0; + return 1; +} +EXPORT_SYMBOL(mmc_can_reset); + +static int mmc_do_hw_reset(struct mmc_host *host, int check) +{ + struct mmc_card *card = host->card; + + if (!host->bus_ops->power_restore) + return -EOPNOTSUPP; + + if (!(host->caps & MMC_CAP_HW_RESET) || !host->ops->hw_reset) + return -EOPNOTSUPP; + + if (!card) + return -EINVAL; + + if (!mmc_can_reset(card)) + return -EOPNOTSUPP; + + mmc_host_clk_hold(host); + mmc_set_clock(host, host->f_init); + + host->ops->hw_reset(host); + + /* If the reset has happened, then a status command will fail */ + if (check) { + struct mmc_command cmd = {0}; + int err; + + cmd.opcode = MMC_SEND_STATUS; + 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, 0); + if (!err) { + mmc_host_clk_release(host); + return -ENOSYS; + } + } + + host->card->state &= ~(MMC_STATE_HIGHSPEED | MMC_STATE_HIGHSPEED_DDR); + 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.bus_width = MMC_BUS_WIDTH_1; + host->ios.timing = MMC_TIMING_LEGACY; + mmc_set_ios(host); + + mmc_host_clk_release(host); + + return host->bus_ops->power_restore(host); +} + +int mmc_hw_reset(struct mmc_host *host) +{ + return mmc_do_hw_reset(host, 0); +} +EXPORT_SYMBOL(mmc_hw_reset); + +int mmc_hw_reset_check(struct mmc_host *host) +{ + return mmc_do_hw_reset(host, 1); +} +EXPORT_SYMBOL(mmc_hw_reset_check); + static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq) { host->f_init = freq; @@ -1786,6 +1874,12 @@ static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq) #endif mmc_power_up(host); + /* + * Some eMMCs (with VCCQ always on) may not be reset after power up, so + * do a hardware reset if possible. + */ + mmc_hw_reset_for_init(host); + /* * sdio_reset sends CMD52 to reset card. Since we do not know * if the card is being re-initialized, just send it. CMD52 diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index b74e6f14b3ac..7adc30da8366 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -402,8 +402,10 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) ext_csd[EXT_CSD_TRIM_MULT]; } - if (card->ext_csd.rev >= 5) + if (card->ext_csd.rev >= 5) { card->ext_csd.rel_param = ext_csd[EXT_CSD_WR_REL_PARAM]; + card->ext_csd.rst_n_function = ext_csd[EXT_CSD_RST_N_FUNCTION]; + } if (ext_csd[EXT_CSD_ERASED_MEM_CONT]) card->erased_byte = 0xFF; diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 6dfb293326e2..5294ddf382ae 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -50,6 +50,7 @@ struct mmc_ext_csd { u8 rel_sectors; u8 rel_param; u8 part_config; + u8 rst_n_function; unsigned int part_time; /* Units: ms */ unsigned int sa_timeout; /* Units: 100ns */ unsigned int hs_max_dtr; diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index b8b1b7a311f1..56e5625b6f41 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -162,6 +162,9 @@ extern int mmc_erase_group_aligned(struct mmc_card *card, unsigned int from, extern unsigned int mmc_calc_max_discard(struct mmc_card *card); extern int mmc_set_blocklen(struct mmc_card *card, unsigned int blocklen); +extern int mmc_hw_reset(struct mmc_host *host); +extern int mmc_hw_reset_check(struct mmc_host *host); +extern int mmc_can_reset(struct mmc_card *card); extern void mmc_set_data_timeout(struct mmc_data *, const struct mmc_card *); extern unsigned int mmc_align_data_size(struct mmc_card *, unsigned int); diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 340cc0c9409f..b2aefea97048 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -151,6 +151,7 @@ struct mmc_host_ops { int (*execute_tuning)(struct mmc_host *host); void (*enable_preset_value)(struct mmc_host *host, bool enable); int (*select_drive_strength)(unsigned int max_dtr, int host_drv, int card_drv); + void (*hw_reset)(struct mmc_host *host); }; struct mmc_card; @@ -233,6 +234,7 @@ struct mmc_host { #define MMC_CAP_MAX_CURRENT_600 (1 << 28) /* Host max current limit is 600mA */ #define MMC_CAP_MAX_CURRENT_800 (1 << 29) /* Host max current limit is 800mA */ #define MMC_CAP_CMD23 (1 << 30) /* CMD23 supported. */ +#define MMC_CAP_HW_RESET (1 << 31) /* Hardware reset */ mmc_pm_flag_t pm_caps; /* supported pm features */ diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index 5a794cb503ea..ed8fca890ee2 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -272,6 +272,7 @@ struct _mmc_csd { #define EXT_CSD_PARTITION_ATTRIBUTE 156 /* R/W */ #define EXT_CSD_PARTITION_SUPPORT 160 /* RO */ +#define EXT_CSD_RST_N_FUNCTION 162 /* R/W */ #define EXT_CSD_WR_REL_PARAM 166 /* RO */ #define EXT_CSD_ERASE_GROUP_DEF 175 /* R/W */ #define EXT_CSD_PART_CONFIG 179 /* R/W */ @@ -328,6 +329,9 @@ struct _mmc_csd { #define EXT_CSD_SEC_BD_BLK_EN BIT(2) #define EXT_CSD_SEC_GB_CL_EN BIT(4) +#define EXT_CSD_RST_N_EN_MASK 0x3 +#define EXT_CSD_RST_N_ENABLED 1 /* RST_n is enabled on card */ + /* * MMC_SWITCH access modes */ -- cgit v1.2.3 From 714c4a6e3a0f730834ec8a8bc83b2a6da33f54dc Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Tue, 30 Aug 2011 18:26:39 +0200 Subject: mmc: sh_mmcif: simplify platform data Provide platforms with a simplified way to specify MMCIF DMA slave IDs in a way, similar to SDHI and other sh_dma clients. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Chris Ball --- drivers/mmc/host/sh_mmcif.c | 20 ++++++++++++++++---- include/linux/mmc/sh_mmcif.h | 4 +++- 2 files changed, 19 insertions(+), 5 deletions(-) (limited to 'include') diff --git a/drivers/mmc/host/sh_mmcif.c b/drivers/mmc/host/sh_mmcif.c index 557886bee9ce..bd91c94ea18e 100644 --- a/drivers/mmc/host/sh_mmcif.c +++ b/drivers/mmc/host/sh_mmcif.c @@ -165,6 +165,8 @@ struct sh_mmcif_host { struct mmc_host *mmc; struct mmc_data *data; struct platform_device *pd; + struct sh_dmae_slave dma_slave_tx; + struct sh_dmae_slave dma_slave_rx; struct clk *hclk; unsigned int clk; int bus_width; @@ -323,25 +325,35 @@ static bool sh_mmcif_filter(struct dma_chan *chan, void *arg) static void sh_mmcif_request_dma(struct sh_mmcif_host *host, struct sh_mmcif_plat_data *pdata) { + struct sh_dmae_slave *tx, *rx; host->dma_active = false; /* We can only either use DMA for both Tx and Rx or not use it at all */ if (pdata->dma) { + dev_warn(&host->pd->dev, + "Update your platform to use embedded DMA slave IDs\n"); + tx = &pdata->dma->chan_priv_tx; + rx = &pdata->dma->chan_priv_rx; + } else { + tx = &host->dma_slave_tx; + tx->slave_id = pdata->slave_id_tx; + rx = &host->dma_slave_rx; + rx->slave_id = pdata->slave_id_rx; + } + if (tx->slave_id > 0 && rx->slave_id > 0) { dma_cap_mask_t mask; dma_cap_zero(mask); dma_cap_set(DMA_SLAVE, mask); - host->chan_tx = dma_request_channel(mask, sh_mmcif_filter, - &pdata->dma->chan_priv_tx); + host->chan_tx = dma_request_channel(mask, sh_mmcif_filter, tx); dev_dbg(&host->pd->dev, "%s: TX: got channel %p\n", __func__, host->chan_tx); if (!host->chan_tx) return; - host->chan_rx = dma_request_channel(mask, sh_mmcif_filter, - &pdata->dma->chan_priv_rx); + host->chan_rx = dma_request_channel(mask, sh_mmcif_filter, rx); dev_dbg(&host->pd->dev, "%s: RX: got channel %p\n", __func__, host->chan_rx); diff --git a/include/linux/mmc/sh_mmcif.h b/include/linux/mmc/sh_mmcif.h index 0222cd8ebe76..04ff452bf5c3 100644 --- a/include/linux/mmc/sh_mmcif.h +++ b/include/linux/mmc/sh_mmcif.h @@ -41,7 +41,9 @@ struct sh_mmcif_plat_data { void (*set_pwr)(struct platform_device *pdev, int state); void (*down_pwr)(struct platform_device *pdev); int (*get_cd)(struct platform_device *pdef); - struct sh_mmcif_dma *dma; + struct sh_mmcif_dma *dma; /* Deprecated. Instead */ + unsigned int slave_id_tx; /* use embedded slave_id_[tr]x */ + unsigned int slave_id_rx; u8 sup_pclk; /* 1 :SH7757, 0: SH7724/SH7372 */ unsigned long caps; u32 ocr; -- cgit v1.2.3 From b87d8dbf6c410b5f2d9b6893c85baa06aa131c7c Mon Sep 17 00:00:00 2001 From: Girish K S Date: Fri, 23 Sep 2011 20:41:47 +0530 Subject: mmc: core: eMMC 4.5 Power Class Selection Feature This patch adds the power class selection feature available for mmc versions 4.0 and above. During the enumeration stage before switching to the lower data bus, check if the power class is supported for the current bus width. If the power class is available then switch to the power class and use the higher data bus. If power class is not supported then switch to the lower data bus in a worst case. Signed-off-by: Girish K S Signed-off-by: Chris Ball --- drivers/mmc/core/mmc.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/mmc/mmc.h | 14 ++++++++ 2 files changed, 110 insertions(+) (limited to 'include') diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 7adc30da8366..c2334d636fe8 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -531,6 +531,86 @@ static struct device_type mmc_type = { .groups = mmc_attr_groups, }; +/* + * Select the PowerClass for the current bus width + * If power class is defined for 4/8 bit bus in the + * extended CSD register, select it by executing the + * mmc_switch command. + */ +static int mmc_select_powerclass(struct mmc_card *card, + unsigned int bus_width, u8 *ext_csd) +{ + int err = 0; + unsigned int pwrclass_val; + unsigned int index = 0; + struct mmc_host *host; + + BUG_ON(!card); + + host = card->host; + BUG_ON(!host); + + if (ext_csd == NULL) + return 0; + + /* Power class selection is supported for versions >= 4.0 */ + if (card->csd.mmca_vsn < CSD_SPEC_VER_4) + return 0; + + /* Power class values are defined only for 4/8 bit bus */ + if (bus_width == EXT_CSD_BUS_WIDTH_1) + return 0; + + switch (1 << host->ios.vdd) { + case MMC_VDD_165_195: + if (host->ios.clock <= 26000000) + index = EXT_CSD_PWR_CL_26_195; + else if (host->ios.clock <= 52000000) + index = (bus_width <= EXT_CSD_BUS_WIDTH_8) ? + EXT_CSD_PWR_CL_52_195 : + EXT_CSD_PWR_CL_DDR_52_195; + else if (host->ios.clock <= 200000000) + index = EXT_CSD_PWR_CL_200_195; + break; + case MMC_VDD_32_33: + case MMC_VDD_33_34: + case MMC_VDD_34_35: + case MMC_VDD_35_36: + if (host->ios.clock <= 26000000) + index = EXT_CSD_PWR_CL_26_360; + else if (host->ios.clock <= 52000000) + index = (bus_width <= EXT_CSD_BUS_WIDTH_8) ? + EXT_CSD_PWR_CL_52_360 : + EXT_CSD_PWR_CL_DDR_52_360; + else if (host->ios.clock <= 200000000) + index = EXT_CSD_PWR_CL_200_360; + break; + default: + pr_warning("%s: Voltage range not supported " + "for power class.\n", mmc_hostname(host)); + return -EINVAL; + } + + pwrclass_val = ext_csd[index]; + + if (bus_width & (EXT_CSD_BUS_WIDTH_8 | EXT_CSD_DDR_BUS_WIDTH_8)) + pwrclass_val = (pwrclass_val & EXT_CSD_PWR_CL_8BIT_MASK) >> + EXT_CSD_PWR_CL_8BIT_SHIFT; + else + pwrclass_val = (pwrclass_val & EXT_CSD_PWR_CL_4BIT_MASK) >> + EXT_CSD_PWR_CL_4BIT_SHIFT; + + /* If the power class is different from the default value */ + if (pwrclass_val > 0) { + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_POWER_CLASS, + pwrclass_val, + 0); + } + + return err; +} + /* * Handle the detection and initialisation of a card. * @@ -787,6 +867,14 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, bus_width = bus_widths[idx]; if (bus_width == MMC_BUS_WIDTH_1) ddr = 0; /* no DDR for 1-bit width */ + err = mmc_select_powerclass(card, ext_csd_bits[idx][0], + ext_csd); + if (err) + pr_err("%s: power class selection to " + "bus width %d failed\n", + mmc_hostname(card->host), + 1 << bus_width); + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BUS_WIDTH, ext_csd_bits[idx][0], @@ -810,6 +898,14 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, } if (!err && ddr) { + err = mmc_select_powerclass(card, ext_csd_bits[idx][1], + ext_csd); + if (err) + pr_err("%s: power class selection to " + "bus width %d ddr %d failed\n", + mmc_hostname(card->host), + 1 << bus_width, ddr); + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BUS_WIDTH, ext_csd_bits[idx][1], diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index ed8fca890ee2..50af22730c74 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -279,10 +279,15 @@ struct _mmc_csd { #define EXT_CSD_ERASED_MEM_CONT 181 /* RO */ #define EXT_CSD_BUS_WIDTH 183 /* R/W */ #define EXT_CSD_HS_TIMING 185 /* R/W */ +#define EXT_CSD_POWER_CLASS 187 /* R/W */ #define EXT_CSD_REV 192 /* RO */ #define EXT_CSD_STRUCTURE 194 /* RO */ #define EXT_CSD_CARD_TYPE 196 /* RO */ #define EXT_CSD_PART_SWITCH_TIME 199 /* RO */ +#define EXT_CSD_PWR_CL_52_195 200 /* RO */ +#define EXT_CSD_PWR_CL_26_195 201 /* RO */ +#define EXT_CSD_PWR_CL_52_360 202 /* RO */ +#define EXT_CSD_PWR_CL_26_360 203 /* RO */ #define EXT_CSD_SEC_CNT 212 /* RO, 4 bytes */ #define EXT_CSD_S_A_TIMEOUT 217 /* RO */ #define EXT_CSD_REL_WR_SEC_C 222 /* RO */ @@ -294,6 +299,11 @@ struct _mmc_csd { #define EXT_CSD_SEC_ERASE_MULT 230 /* RO */ #define EXT_CSD_SEC_FEATURE_SUPPORT 231 /* RO */ #define EXT_CSD_TRIM_MULT 232 /* RO */ +#define EXT_CSD_PWR_CL_200_195 236 /* RO */ +#define EXT_CSD_PWR_CL_200_360 237 /* RO */ +#define EXT_CSD_PWR_CL_DDR_52_195 238 /* RO */ +#define EXT_CSD_PWR_CL_DDR_52_360 239 /* RO */ +#define EXT_CSD_POWER_OFF_LONG_TIME 247 /* RO */ /* * EXT_CSD field definitions @@ -332,6 +342,10 @@ struct _mmc_csd { #define EXT_CSD_RST_N_EN_MASK 0x3 #define EXT_CSD_RST_N_ENABLED 1 /* RST_n is enabled on card */ +#define EXT_CSD_PWR_CL_8BIT_MASK 0xF0 /* 8 bit PWR CLS */ +#define EXT_CSD_PWR_CL_4BIT_MASK 0x0F /* 8 bit PWR CLS */ +#define EXT_CSD_PWR_CL_8BIT_SHIFT 4 +#define EXT_CSD_PWR_CL_4BIT_SHIFT 0 /* * MMC_SWITCH access modes */ -- cgit v1.2.3 From f7c56ef2af5ae7e4c24c3c79427b38d18ba1d294 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Fri, 23 Sep 2011 12:48:21 +0300 Subject: mmc: block: support no access to boot partitions Intel Medfield platform blocks access to eMMC boot partitions which results in switch errors. Since there is no access, mmcboot0/1 devices should not be created. Add a host capability to reflect that. Signed-off-by: Adrian Hunter Signed-off-by: Chris Ball --- drivers/mmc/card/block.c | 2 +- drivers/mmc/host/sdhci-pci.c | 2 ++ include/linux/mmc/host.h | 10 ++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index d7eb2c082336..aa66d3f3abb1 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -1480,7 +1480,7 @@ static int mmc_blk_alloc_parts(struct mmc_card *card, struct mmc_blk_data *md) if (!mmc_card_mmc(card)) return 0; - if (card->ext_csd.boot_size) { + if (card->ext_csd.boot_size && mmc_boot_partition_access(card->host)) { ret = mmc_blk_alloc_part(card, md, EXT_CSD_PART_CONFIG_ACC_BOOT0, card->ext_csd.boot_size >> 9, true, diff --git a/drivers/mmc/host/sdhci-pci.c b/drivers/mmc/host/sdhci-pci.c index 3b30c515cbc2..f8a17f98f3a9 100644 --- a/drivers/mmc/host/sdhci-pci.c +++ b/drivers/mmc/host/sdhci-pci.c @@ -215,6 +215,8 @@ static int mfd_emmc_probe_slot(struct sdhci_pci_slot *slot) slot->host->mmc->caps |= MMC_CAP_8_BIT_DATA; + slot->host->mmc->caps2 = MMC_CAP2_BOOTPART_NOACC; + return 0; } diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index b2aefea97048..aed5bc7245f7 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -236,6 +236,10 @@ struct mmc_host { #define MMC_CAP_CMD23 (1 << 30) /* CMD23 supported. */ #define MMC_CAP_HW_RESET (1 << 31) /* Hardware reset */ + unsigned int caps2; /* More host capabilities */ + +#define MMC_CAP2_BOOTPART_NOACC (1 << 0) /* Boot partition no access */ + mmc_pm_flag_t pm_caps; /* supported pm features */ #ifdef CONFIG_MMC_CLKGATE @@ -404,4 +408,10 @@ static inline int mmc_host_cmd23(struct mmc_host *host) { return host->caps & MMC_CAP_CMD23; } + +static inline int mmc_boot_partition_access(struct mmc_host *host) +{ + return !(host->caps2 & MMC_CAP2_BOOTPART_NOACC); +} + #endif /* LINUX_MMC_HOST_H */ -- cgit v1.2.3 From e0c368d571d946ff40f068344b5c2df90c93dd2e Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Thu, 6 Oct 2011 23:41:38 +0900 Subject: mmc: core: general purpose MMC partition support. It allows gerneral purpose partitions in MMC Device. And I try to simply make mmc_blk_alloc_parts using mmc_part structure suggested by Andrei Warkentin. After patching, we see general purpose partitions like this: > cat /proc/partitions 179 0 847872 mmcblk0 179 192 4096 mmcblk0gp3 179 160 4096 mmcblk0gp2 179 128 4096 mmcblk0gp1 179 96 1052672 mmcblk0gp0 179 64 1024 mmcblk0boot1 179 32 1024 mmcblk0boot0 Signed-off-by: Namjae Jeon Acked-by: Andrei Warkentin Signed-off-by: Chris Ball --- drivers/mmc/card/block.c | 31 ++++++++++++++++------------- drivers/mmc/core/mmc.c | 52 ++++++++++++++++++++++++++++++++++++++++++++---- include/linux/mmc/card.h | 34 ++++++++++++++++++++++++++++++- include/linux/mmc/mmc.h | 5 ++++- 4 files changed, 102 insertions(+), 20 deletions(-) (limited to 'include') diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index aa66d3f3abb1..2cf1ba6db910 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -1473,26 +1473,29 @@ static int mmc_blk_alloc_part(struct mmc_card *card, return 0; } +/* MMC Physical partitions consist of two boot partitions and + * up to four general purpose partitions. + * For each partition enabled in EXT_CSD a block device will be allocatedi + * to provide access to the partition. + */ + static int mmc_blk_alloc_parts(struct mmc_card *card, struct mmc_blk_data *md) { - int ret = 0; + int idx, ret = 0; if (!mmc_card_mmc(card)) return 0; - if (card->ext_csd.boot_size && mmc_boot_partition_access(card->host)) { - ret = mmc_blk_alloc_part(card, md, EXT_CSD_PART_CONFIG_ACC_BOOT0, - card->ext_csd.boot_size >> 9, - true, - "boot0"); - if (ret) - return ret; - ret = mmc_blk_alloc_part(card, md, EXT_CSD_PART_CONFIG_ACC_BOOT1, - card->ext_csd.boot_size >> 9, - true, - "boot1"); - if (ret) - return ret; + for (idx = 0; idx < card->nr_parts; idx++) { + if (card->part[idx].size) { + ret = mmc_blk_alloc_part(card, md, + card->part[idx].part_cfg, + card->part[idx].size >> 9, + card->part[idx].force_ro, + card->part[idx].name); + if (ret) + return ret; + } } return ret; diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index c632b1faf70d..2a4c9a4d3c00 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -239,7 +239,9 @@ static int mmc_get_ext_csd(struct mmc_card *card, u8 **new_ext_csd) */ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) { - int err = 0; + int err = 0, idx; + unsigned int part_size; + u8 hc_erase_grp_sz = 0, hc_wp_grp_sz = 0; BUG_ON(!card); @@ -340,7 +342,14 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) * There are two boot regions of equal size, defined in * multiples of 128K. */ - card->ext_csd.boot_size = ext_csd[EXT_CSD_BOOT_MULT] << 17; + if (ext_csd[EXT_CSD_BOOT_MULT] && mmc_boot_partition_access(card->host)) { + for (idx = 0; idx < MMC_NUM_BOOT_PARTITION; idx++) { + part_size = ext_csd[EXT_CSD_BOOT_MULT] << 17; + mmc_part_add(card, part_size, + EXT_CSD_PART_CONFIG_ACC_BOOT0 + idx, + "boot%d", idx, true); + } + } } card->ext_csd.raw_hc_erase_gap_size = @@ -362,9 +371,9 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) card->ext_csd.raw_partition_support = ext_csd[EXT_CSD_PARTITION_SUPPORT]; if ((ext_csd[EXT_CSD_PARTITION_SUPPORT] & 0x2) && (ext_csd[EXT_CSD_PARTITION_ATTRIBUTE] & 0x1)) { - u8 hc_erase_grp_sz = + hc_erase_grp_sz = ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE]; - u8 hc_wp_grp_sz = + hc_wp_grp_sz = ext_csd[EXT_CSD_HC_WP_GRP_SIZE]; card->ext_csd.enhanced_area_en = 1; @@ -393,6 +402,41 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) card->ext_csd.enhanced_area_offset = -EINVAL; card->ext_csd.enhanced_area_size = -EINVAL; } + + /* + * General purpose partition feature support -- + * If ext_csd has the size of general purpose partitions, + * set size, part_cfg, partition name in mmc_part. + */ + if (ext_csd[EXT_CSD_PARTITION_SUPPORT] & + EXT_CSD_PART_SUPPORT_PART_EN) { + if (card->ext_csd.enhanced_area_en != 1) { + hc_erase_grp_sz = + ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE]; + hc_wp_grp_sz = + ext_csd[EXT_CSD_HC_WP_GRP_SIZE]; + + card->ext_csd.enhanced_area_en = 1; + } + + for (idx = 0; idx < MMC_NUM_GP_PARTITION; idx++) { + if (!ext_csd[EXT_CSD_GP_SIZE_MULT + idx * 3] && + !ext_csd[EXT_CSD_GP_SIZE_MULT + idx * 3 + 1] && + !ext_csd[EXT_CSD_GP_SIZE_MULT + idx * 3 + 2]) + continue; + part_size = + (ext_csd[EXT_CSD_GP_SIZE_MULT + idx * 3 + 2] + << 16) + + (ext_csd[EXT_CSD_GP_SIZE_MULT + idx * 3 + 1] + << 8) + + ext_csd[EXT_CSD_GP_SIZE_MULT + idx * 3]; + part_size *= (size_t)(hc_erase_grp_sz * + hc_wp_grp_sz); + mmc_part_add(card, part_size << 19, + EXT_CSD_PART_CONFIG_ACC_GP0 + idx, + "gp%d", idx, false); + } + } card->ext_csd.sec_trim_mult = ext_csd[EXT_CSD_SEC_TRIM_MULT]; card->ext_csd.sec_erase_mult = diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 5294ddf382ae..92762865f7e0 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -12,6 +12,7 @@ #include #include +#include struct mmc_cid { unsigned int manfid; @@ -64,7 +65,6 @@ struct mmc_ext_csd { bool enhanced_area_en; /* enable bit */ unsigned long long enhanced_area_offset; /* Units: Byte */ unsigned int enhanced_area_size; /* Units: KB */ - unsigned int boot_size; /* in bytes */ u8 raw_partition_support; /* 160 */ u8 raw_erased_mem_count; /* 181 */ u8 raw_ext_csd_structure; /* 194 */ @@ -158,6 +158,23 @@ struct sdio_func_tuple; #define SDIO_MAX_FUNCS 7 +/* The number of MMC physical partitions. These consist of: + * boot partitions (2), general purpose partitions (4) in MMC v4.4. + */ +#define MMC_NUM_BOOT_PARTITION 2 +#define MMC_NUM_GP_PARTITION 4 +#define MMC_NUM_PHY_PARTITION 6 + +/* + * MMC Physical partitions + */ +struct mmc_part { + unsigned int size; /* partition size (in bytes) */ + unsigned int part_cfg; /* partition type */ + char name[DISK_NAME_LEN]; + bool force_ro; /* to make boot parts RO by default */ +}; + /* * MMC device */ @@ -219,8 +236,23 @@ struct mmc_card { unsigned int sd_bus_speed; /* Bus Speed Mode set for the card */ struct dentry *debugfs_root; + struct mmc_part part[MMC_NUM_PHY_PARTITION]; /* physical partitions */ + unsigned int nr_parts; }; +/* + * This function fill contents in mmc_part. + */ +static inline void mmc_part_add(struct mmc_card *card, unsigned int size, + unsigned int part_cfg, char *name, int idx, bool ro) +{ + card->part[card->nr_parts].size = size; + card->part[card->nr_parts].part_cfg = part_cfg; + sprintf(card->part[card->nr_parts].name, name, idx); + card->part[card->nr_parts].force_ro = ro; + card->nr_parts++; +} + /* * The world is not perfect and supplies us with broken mmc/sdio devices. * For at least some of these bugs we need a work-around. diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index 50af22730c74..ea558eb44c79 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -270,6 +270,7 @@ struct _mmc_csd { * EXT_CSD fields */ +#define EXT_CSD_GP_SIZE_MULT 143 /* R/W */ #define EXT_CSD_PARTITION_ATTRIBUTE 156 /* R/W */ #define EXT_CSD_PARTITION_SUPPORT 160 /* RO */ #define EXT_CSD_RST_N_FUNCTION 162 /* R/W */ @@ -313,7 +314,9 @@ struct _mmc_csd { #define EXT_CSD_PART_CONFIG_ACC_MASK (0x7) #define EXT_CSD_PART_CONFIG_ACC_BOOT0 (0x1) -#define EXT_CSD_PART_CONFIG_ACC_BOOT1 (0x2) +#define EXT_CSD_PART_CONFIG_ACC_GP0 (0x4) + +#define EXT_CSD_PART_SUPPORT_PART_EN (0x1) #define EXT_CSD_CMD_SET_NORMAL (1<<0) #define EXT_CSD_CMD_SET_SECURE (1<<1) -- cgit v1.2.3 From 66fd8ad5100b5003046aa744a4f12fa31bb831f9 Mon Sep 17 00:00:00 2001 From: Adrian Hunter Date: Mon, 3 Oct 2011 15:33:34 +0300 Subject: mmc: sdhci-pci: add runtime pm support Ths patch allows runtime PM for sdhci-pci, runtime suspending after inactivity of 50ms and ensuring runtime resume before SDHC registers are accessed. During runtime suspend, interrupts are masked. The host controller state is restored at runtime resume. For Medfield, the host controller's card detect mechanism is supplanted by an always-on GPIO which provides for card detect wake-up. Signed-off-by: Adrian Hunter Signed-off-by: Chris Ball --- drivers/mmc/host/sdhci-pci.c | 182 +++++++++++++++++++++++++++++- drivers/mmc/host/sdhci.c | 255 +++++++++++++++++++++++++++++++++++-------- drivers/mmc/host/sdhci.h | 5 + include/linux/mmc/sdhci.h | 8 ++ 4 files changed, 405 insertions(+), 45 deletions(-) (limited to 'include') diff --git a/drivers/mmc/host/sdhci-pci.c b/drivers/mmc/host/sdhci-pci.c index 4487b8430aaf..f49b184308c0 100644 --- a/drivers/mmc/host/sdhci-pci.c +++ b/drivers/mmc/host/sdhci-pci.c @@ -24,6 +24,7 @@ #include #include #include +#include #include "sdhci.h" @@ -63,6 +64,8 @@ struct sdhci_pci_slot { int pci_bar; int rst_n_gpio; + int cd_gpio; + int cd_irq; }; struct sdhci_pci_chip { @@ -190,6 +193,70 @@ static int mfd_emmc_gpio_parse(struct sfi_table_header *table) return 0; } +#ifdef CONFIG_PM_RUNTIME + +static irqreturn_t mfd_sd_cd(int irq, void *dev_id) +{ + struct sdhci_pci_slot *slot = dev_id; + struct sdhci_host *host = slot->host; + + mmc_detect_change(host->mmc, msecs_to_jiffies(200)); + return IRQ_HANDLED; +} + +#define MFLD_SD_CD_PIN 69 + +static int mfd_sd_probe_slot(struct sdhci_pci_slot *slot) +{ + int err, irq, gpio = MFLD_SD_CD_PIN; + + slot->cd_gpio = -EINVAL; + slot->cd_irq = -EINVAL; + + err = gpio_request(gpio, "sd_cd"); + if (err < 0) + goto out; + + err = gpio_direction_input(gpio); + if (err < 0) + goto out_free; + + irq = gpio_to_irq(gpio); + if (irq < 0) + goto out_free; + + err = request_irq(irq, mfd_sd_cd, IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, "sd_cd", slot); + if (err) + goto out_free; + + slot->cd_gpio = gpio; + slot->cd_irq = irq; + slot->host->quirks2 |= SDHCI_QUIRK2_OWN_CARD_DETECTION; + + return 0; + +out_free: + gpio_free(gpio); +out: + dev_warn(&slot->chip->pdev->dev, "failed to setup card detect wake up\n"); + return 0; +} + +static void mfd_sd_remove_slot(struct sdhci_pci_slot *slot, int dead) +{ + if (slot->cd_irq >= 0) + free_irq(slot->cd_irq, slot); + gpio_free(slot->cd_gpio); +} + +#else + +#define mfd_sd_probe_slot NULL +#define mfd_sd_remove_slot NULL + +#endif + static int mfd_emmc_probe_slot(struct sdhci_pci_slot *slot) { const char *name = NULL; @@ -214,7 +281,7 @@ static int mfd_emmc_probe_slot(struct sdhci_pci_slot *slot) slot->host->mmc->caps |= MMC_CAP_HW_RESET; } - slot->host->mmc->caps |= MMC_CAP_8_BIT_DATA; + slot->host->mmc->caps |= MMC_CAP_8_BIT_DATA | MMC_CAP_NONREMOVABLE; slot->host->mmc->caps2 = MMC_CAP2_BOOTPART_NOACC; @@ -238,6 +305,8 @@ static const struct sdhci_pci_fixes sdhci_intel_mrst_hc1_hc2 = { static const struct sdhci_pci_fixes sdhci_intel_mfd_sd = { .quirks = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC, + .probe_slot = mfd_sd_probe_slot, + .remove_slot = mfd_sd_remove_slot, }; static const struct sdhci_pci_fixes sdhci_intel_mfd_sdio = { @@ -1018,6 +1087,95 @@ static int sdhci_pci_resume(struct pci_dev *pdev) #endif /* CONFIG_PM */ +#ifdef CONFIG_PM_RUNTIME + +static int sdhci_pci_runtime_suspend(struct device *dev) +{ + struct pci_dev *pdev = container_of(dev, struct pci_dev, dev); + struct sdhci_pci_chip *chip; + struct sdhci_pci_slot *slot; + pm_message_t state = { .event = PM_EVENT_SUSPEND }; + int i, ret; + + chip = pci_get_drvdata(pdev); + if (!chip) + return 0; + + for (i = 0; i < chip->num_slots; i++) { + slot = chip->slots[i]; + if (!slot) + continue; + + ret = sdhci_runtime_suspend_host(slot->host); + + if (ret) { + for (i--; i >= 0; i--) + sdhci_runtime_resume_host(chip->slots[i]->host); + return ret; + } + } + + if (chip->fixes && chip->fixes->suspend) { + ret = chip->fixes->suspend(chip, state); + if (ret) { + for (i = chip->num_slots - 1; i >= 0; i--) + sdhci_runtime_resume_host(chip->slots[i]->host); + return ret; + } + } + + return 0; +} + +static int sdhci_pci_runtime_resume(struct device *dev) +{ + struct pci_dev *pdev = container_of(dev, struct pci_dev, dev); + struct sdhci_pci_chip *chip; + struct sdhci_pci_slot *slot; + int i, ret; + + chip = pci_get_drvdata(pdev); + if (!chip) + return 0; + + if (chip->fixes && chip->fixes->resume) { + ret = chip->fixes->resume(chip); + if (ret) + return ret; + } + + for (i = 0; i < chip->num_slots; i++) { + slot = chip->slots[i]; + if (!slot) + continue; + + ret = sdhci_runtime_resume_host(slot->host); + if (ret) + return ret; + } + + return 0; +} + +static int sdhci_pci_runtime_idle(struct device *dev) +{ + return 0; +} + +#else + +#define sdhci_pci_runtime_suspend NULL +#define sdhci_pci_runtime_resume NULL +#define sdhci_pci_runtime_idle NULL + +#endif + +static const struct dev_pm_ops sdhci_pci_pm_ops = { + .runtime_suspend = sdhci_pci_runtime_suspend, + .runtime_resume = sdhci_pci_runtime_resume, + .runtime_idle = sdhci_pci_runtime_idle, +}; + /*****************************************************************************\ * * * Device probing/removal * @@ -1133,6 +1291,21 @@ static void sdhci_pci_remove_slot(struct sdhci_pci_slot *slot) sdhci_free_host(slot->host); } +static void __devinit sdhci_pci_runtime_pm_allow(struct device *dev) +{ + pm_runtime_put_noidle(dev); + pm_runtime_allow(dev); + pm_runtime_set_autosuspend_delay(dev, 50); + pm_runtime_use_autosuspend(dev); + pm_suspend_ignore_children(dev, 1); +} + +static void __devexit sdhci_pci_runtime_pm_forbid(struct device *dev) +{ + pm_runtime_forbid(dev); + pm_runtime_get_noresume(dev); +} + static int __devinit sdhci_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { @@ -1208,6 +1381,8 @@ static int __devinit sdhci_pci_probe(struct pci_dev *pdev, chip->slots[i] = slot; } + sdhci_pci_runtime_pm_allow(&pdev->dev); + return 0; free: @@ -1224,6 +1399,8 @@ static void __devexit sdhci_pci_remove(struct pci_dev *pdev) int i; struct sdhci_pci_chip *chip; + sdhci_pci_runtime_pm_forbid(&pdev->dev); + chip = pci_get_drvdata(pdev); if (chip) { @@ -1244,6 +1421,9 @@ static struct pci_driver sdhci_driver = { .remove = __devexit_p(sdhci_pci_remove), .suspend = sdhci_pci_suspend, .resume = sdhci_pci_resume, + .driver = { + .pm = &sdhci_pci_pm_ops + }, }; /*****************************************************************************\ diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 9394860602fe..155deb8629af 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -21,6 +21,7 @@ #include #include #include +#include #include @@ -42,6 +43,7 @@ #define MAX_TUNING_LOOP 40 static unsigned int debug_quirks = 0; +static unsigned int debug_quirks2; static void sdhci_finish_data(struct sdhci_host *); @@ -50,6 +52,20 @@ static void sdhci_finish_command(struct sdhci_host *); static int sdhci_execute_tuning(struct mmc_host *mmc); static void sdhci_tuning_timer(unsigned long data); +#ifdef CONFIG_PM_RUNTIME +static int sdhci_runtime_pm_get(struct sdhci_host *host); +static int sdhci_runtime_pm_put(struct sdhci_host *host); +#else +static inline int sdhci_runtime_pm_get(struct sdhci_host *host) +{ + return 0; +} +static inline int sdhci_runtime_pm_put(struct sdhci_host *host) +{ + return 0; +} +#endif + static void sdhci_dumpregs(struct sdhci_host *host) { printk(KERN_DEBUG DRIVER_NAME ": =========== REGISTER DUMP (%s)===========\n", @@ -133,6 +149,9 @@ static void sdhci_set_card_detection(struct sdhci_host *host, bool enable) if (host->quirks & SDHCI_QUIRK_BROKEN_CARD_DETECTION) return; + if (host->quirks2 & SDHCI_QUIRK2_OWN_CARD_DETECTION) + return; + present = sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT; irqs = present ? SDHCI_INT_CARD_REMOVE : SDHCI_INT_CARD_INSERT; @@ -252,11 +271,14 @@ static void sdhci_led_control(struct led_classdev *led, spin_lock_irqsave(&host->lock, flags); + if (host->runtime_suspended) + goto out; + if (brightness == LED_OFF) sdhci_deactivate_led(host); else sdhci_activate_led(host); - +out: spin_unlock_irqrestore(&host->lock, flags); } #endif @@ -1210,6 +1232,8 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) host = mmc_priv(mmc); + sdhci_runtime_pm_get(host); + spin_lock_irqsave(&host->lock, flags); WARN_ON(host->mrq != NULL); @@ -1270,14 +1294,11 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) spin_unlock_irqrestore(&host->lock, flags); } -static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +static void sdhci_do_set_ios(struct sdhci_host *host, struct mmc_ios *ios) { - struct sdhci_host *host; unsigned long flags; u8 ctrl; - host = mmc_priv(mmc); - spin_lock_irqsave(&host->lock, flags); if (host->flags & SDHCI_DEVICE_DEAD) @@ -1427,7 +1448,16 @@ out: spin_unlock_irqrestore(&host->lock, flags); } -static int check_ro(struct sdhci_host *host) +static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct sdhci_host *host = mmc_priv(mmc); + + sdhci_runtime_pm_get(host); + sdhci_do_set_ios(host, ios); + sdhci_runtime_pm_put(host); +} + +static int sdhci_check_ro(struct sdhci_host *host) { unsigned long flags; int is_readonly; @@ -1451,19 +1481,16 @@ static int check_ro(struct sdhci_host *host) #define SAMPLE_COUNT 5 -static int sdhci_get_ro(struct mmc_host *mmc) +static int sdhci_do_get_ro(struct sdhci_host *host) { - struct sdhci_host *host; int i, ro_count; - host = mmc_priv(mmc); - if (!(host->quirks & SDHCI_QUIRK_UNSTABLE_RO_DETECT)) - return check_ro(host); + return sdhci_check_ro(host); ro_count = 0; for (i = 0; i < SAMPLE_COUNT; i++) { - if (check_ro(host)) { + if (sdhci_check_ro(host)) { if (++ro_count > SAMPLE_COUNT / 2) return 1; } @@ -1480,38 +1507,56 @@ static void sdhci_hw_reset(struct mmc_host *mmc) host->ops->hw_reset(host); } -static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable) +static int sdhci_get_ro(struct mmc_host *mmc) { - struct sdhci_host *host; - unsigned long flags; - - host = mmc_priv(mmc); + struct sdhci_host *host = mmc_priv(mmc); + int ret; - spin_lock_irqsave(&host->lock, flags); + sdhci_runtime_pm_get(host); + ret = sdhci_do_get_ro(host); + sdhci_runtime_pm_put(host); + return ret; +} +static void sdhci_enable_sdio_irq_nolock(struct sdhci_host *host, int enable) +{ if (host->flags & SDHCI_DEVICE_DEAD) goto out; + if (enable) + host->flags |= SDHCI_SDIO_IRQ_ENABLED; + else + host->flags &= ~SDHCI_SDIO_IRQ_ENABLED; + + /* SDIO IRQ will be enabled as appropriate in runtime resume */ + if (host->runtime_suspended) + goto out; + if (enable) sdhci_unmask_irqs(host, SDHCI_INT_CARD_INT); else sdhci_mask_irqs(host, SDHCI_INT_CARD_INT); out: mmiowb(); +} + +static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + struct sdhci_host *host = mmc_priv(mmc); + unsigned long flags; + spin_lock_irqsave(&host->lock, flags); + sdhci_enable_sdio_irq_nolock(host, enable); spin_unlock_irqrestore(&host->lock, flags); } -static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc, - struct mmc_ios *ios) +static int sdhci_do_start_signal_voltage_switch(struct sdhci_host *host, + struct mmc_ios *ios) { - struct sdhci_host *host; u8 pwr; u16 clk, ctrl; u32 present_state; - host = mmc_priv(mmc); - /* * Signal Voltage Switching is only applicable for Host Controllers * v3.00 and above. @@ -1604,6 +1649,20 @@ static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc, return 0; } +static int sdhci_start_signal_voltage_switch(struct mmc_host *mmc, + struct mmc_ios *ios) +{ + struct sdhci_host *host = mmc_priv(mmc); + int err; + + if (host->version < SDHCI_SPEC_300) + return 0; + sdhci_runtime_pm_get(host); + err = sdhci_do_start_signal_voltage_switch(host, ios); + sdhci_runtime_pm_put(host); + return err; +} + static int sdhci_execute_tuning(struct mmc_host *mmc) { struct sdhci_host *host; @@ -1615,6 +1674,7 @@ static int sdhci_execute_tuning(struct mmc_host *mmc) host = mmc_priv(mmc); + sdhci_runtime_pm_get(host); disable_irq(host->irq); spin_lock(&host->lock); @@ -1632,6 +1692,7 @@ static int sdhci_execute_tuning(struct mmc_host *mmc) else { spin_unlock(&host->lock); enable_irq(host->irq); + sdhci_runtime_pm_put(host); return 0; } @@ -1657,7 +1718,7 @@ static int sdhci_execute_tuning(struct mmc_host *mmc) timeout = 150; do { struct mmc_command cmd = {0}; - struct mmc_request mrq = {0}; + struct mmc_request mrq = {NULL}; if (!tuning_loop_counter && !timeout) break; @@ -1775,18 +1836,16 @@ out: sdhci_clear_set_irqs(host, SDHCI_INT_DATA_AVAIL, ier); spin_unlock(&host->lock); enable_irq(host->irq); + sdhci_runtime_pm_put(host); return err; } -static void sdhci_enable_preset_value(struct mmc_host *mmc, bool enable) +static void sdhci_do_enable_preset_value(struct sdhci_host *host, bool enable) { - struct sdhci_host *host; u16 ctrl; unsigned long flags; - host = mmc_priv(mmc); - /* Host Controller v3.00 defines preset value registers */ if (host->version < SDHCI_SPEC_300) return; @@ -1802,14 +1861,25 @@ static void sdhci_enable_preset_value(struct mmc_host *mmc, bool enable) if (enable && !(ctrl & SDHCI_CTRL_PRESET_VAL_ENABLE)) { ctrl |= SDHCI_CTRL_PRESET_VAL_ENABLE; sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); + host->flags |= SDHCI_PV_ENABLED; } else if (!enable && (ctrl & SDHCI_CTRL_PRESET_VAL_ENABLE)) { ctrl &= ~SDHCI_CTRL_PRESET_VAL_ENABLE; sdhci_writew(host, ctrl, SDHCI_HOST_CONTROL2); + host->flags &= ~SDHCI_PV_ENABLED; } spin_unlock_irqrestore(&host->lock, flags); } +static void sdhci_enable_preset_value(struct mmc_host *mmc, bool enable) +{ + struct sdhci_host *host = mmc_priv(mmc); + + sdhci_runtime_pm_get(host); + sdhci_do_enable_preset_value(host, enable); + sdhci_runtime_pm_put(host); +} + static const struct mmc_host_ops sdhci_ops = { .request = sdhci_request, .set_ios = sdhci_set_ios, @@ -1836,19 +1906,19 @@ static void sdhci_tasklet_card(unsigned long param) spin_lock_irqsave(&host->lock, flags); - if (!(sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) { - if (host->mrq) { - printk(KERN_ERR "%s: Card removed during transfer!\n", - mmc_hostname(host->mmc)); - printk(KERN_ERR "%s: Resetting controller.\n", - mmc_hostname(host->mmc)); + /* Check host->mrq first in case we are runtime suspended */ + if (host->mrq && + !(sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) { + printk(KERN_ERR "%s: Card removed during transfer!\n", + mmc_hostname(host->mmc)); + printk(KERN_ERR "%s: Resetting controller.\n", + mmc_hostname(host->mmc)); - sdhci_reset(host, SDHCI_RESET_CMD); - sdhci_reset(host, SDHCI_RESET_DATA); + sdhci_reset(host, SDHCI_RESET_CMD); + sdhci_reset(host, SDHCI_RESET_DATA); - host->mrq->cmd->error = -ENOMEDIUM; - tasklet_schedule(&host->finish_tasklet); - } + host->mrq->cmd->error = -ENOMEDIUM; + tasklet_schedule(&host->finish_tasklet); } spin_unlock_irqrestore(&host->lock, flags); @@ -1864,14 +1934,16 @@ static void sdhci_tasklet_finish(unsigned long param) host = (struct sdhci_host*)param; + spin_lock_irqsave(&host->lock, flags); + /* * If this tasklet gets rescheduled while running, it will * be run again afterwards but without any active request. */ - if (!host->mrq) + if (!host->mrq) { + spin_unlock_irqrestore(&host->lock, flags); return; - - spin_lock_irqsave(&host->lock, flags); + } del_timer(&host->timer); @@ -1915,6 +1987,7 @@ static void sdhci_tasklet_finish(unsigned long param) spin_unlock_irqrestore(&host->lock, flags); mmc_request_done(host->mmc, mrq); + sdhci_runtime_pm_put(host); } static void sdhci_timeout_timer(unsigned long data) @@ -2146,12 +2219,19 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) static irqreturn_t sdhci_irq(int irq, void *dev_id) { irqreturn_t result; - struct sdhci_host* host = dev_id; + struct sdhci_host *host = dev_id; u32 intmask; int cardint = 0; spin_lock(&host->lock); + if (host->runtime_suspended) { + spin_unlock(&host->lock); + printk(KERN_WARNING "%s: got irq while runtime suspended\n", + mmc_hostname(host->mmc)); + return IRQ_HANDLED; + } + intmask = sdhci_readl(host, SDHCI_INT_STATUS); if (!intmask || intmask == 0xffffffff) { @@ -2285,7 +2365,6 @@ int sdhci_resume_host(struct sdhci_host *host) return ret; } - if (host->flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) { if (host->ops->enable_dma) host->ops->enable_dma(host); @@ -2324,6 +2403,90 @@ EXPORT_SYMBOL_GPL(sdhci_enable_irq_wakeups); #endif /* CONFIG_PM */ +#ifdef CONFIG_PM_RUNTIME + +static int sdhci_runtime_pm_get(struct sdhci_host *host) +{ + return pm_runtime_get_sync(host->mmc->parent); +} + +static int sdhci_runtime_pm_put(struct sdhci_host *host) +{ + pm_runtime_mark_last_busy(host->mmc->parent); + return pm_runtime_put_autosuspend(host->mmc->parent); +} + +int sdhci_runtime_suspend_host(struct sdhci_host *host) +{ + unsigned long flags; + int ret = 0; + + /* Disable tuning since we are suspending */ + if (host->version >= SDHCI_SPEC_300 && + host->tuning_mode == SDHCI_TUNING_MODE_1) { + del_timer_sync(&host->tuning_timer); + host->flags &= ~SDHCI_NEEDS_RETUNING; + } + + spin_lock_irqsave(&host->lock, flags); + sdhci_mask_irqs(host, SDHCI_INT_ALL_MASK); + spin_unlock_irqrestore(&host->lock, flags); + + synchronize_irq(host->irq); + + spin_lock_irqsave(&host->lock, flags); + host->runtime_suspended = true; + spin_unlock_irqrestore(&host->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(sdhci_runtime_suspend_host); + +int sdhci_runtime_resume_host(struct sdhci_host *host) +{ + unsigned long flags; + int ret = 0, host_flags = host->flags; + + if (host_flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) { + if (host->ops->enable_dma) + host->ops->enable_dma(host); + } + + sdhci_init(host, 0); + + /* Force clock and power re-program */ + host->pwr = 0; + host->clock = 0; + sdhci_do_set_ios(host, &host->mmc->ios); + + sdhci_do_start_signal_voltage_switch(host, &host->mmc->ios); + if (host_flags & SDHCI_PV_ENABLED) + sdhci_do_enable_preset_value(host, true); + + /* Set the re-tuning expiration flag */ + if ((host->version >= SDHCI_SPEC_300) && host->tuning_count && + (host->tuning_mode == SDHCI_TUNING_MODE_1)) + host->flags |= SDHCI_NEEDS_RETUNING; + + spin_lock_irqsave(&host->lock, flags); + + host->runtime_suspended = false; + + /* Enable SDIO IRQ */ + if ((host->flags & SDHCI_SDIO_IRQ_ENABLED)) + sdhci_enable_sdio_irq_nolock(host, true); + + /* Enable Card Detection */ + sdhci_enable_card_detection(host); + + spin_unlock_irqrestore(&host->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(sdhci_runtime_resume_host); + +#endif + /*****************************************************************************\ * * * Device allocation/registration * @@ -2366,6 +2529,8 @@ int sdhci_add_host(struct sdhci_host *host) if (debug_quirks) host->quirks = debug_quirks; + if (debug_quirks2) + host->quirks2 = debug_quirks2; sdhci_reset(host, SDHCI_RESET_ALL); @@ -2888,9 +3053,11 @@ module_init(sdhci_drv_init); module_exit(sdhci_drv_exit); module_param(debug_quirks, uint, 0444); +module_param(debug_quirks2, uint, 0444); MODULE_AUTHOR("Pierre Ossman "); MODULE_DESCRIPTION("Secure Digital Host Controller Interface core driver"); MODULE_LICENSE("GPL"); MODULE_PARM_DESC(debug_quirks, "Force certain quirks."); +MODULE_PARM_DESC(debug_quirks2, "Force certain other quirks."); diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 7bd919c33cc2..0a5b65460d8a 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -379,4 +379,9 @@ extern int sdhci_resume_host(struct sdhci_host *host); extern void sdhci_enable_irq_wakeups(struct sdhci_host *host); #endif +#ifdef CONFIG_PM_RUNTIME +extern int sdhci_runtime_suspend_host(struct sdhci_host *host); +extern int sdhci_runtime_resume_host(struct sdhci_host *host); +#endif + #endif /* __SDHCI_HW_H */ diff --git a/include/linux/mmc/sdhci.h b/include/linux/mmc/sdhci.h index 5666f3abfab7..e4b69353678d 100644 --- a/include/linux/mmc/sdhci.h +++ b/include/linux/mmc/sdhci.h @@ -88,6 +88,10 @@ struct sdhci_host { /* The read-only detection via SDHCI_PRESENT_STATE register is unstable */ #define SDHCI_QUIRK_UNSTABLE_RO_DETECT (1<<31) + unsigned int quirks2; /* More deviations from spec. */ + +#define SDHCI_QUIRK2_OWN_CARD_DETECTION (1<<0) + int irq; /* Device IRQ */ void __iomem *ioaddr; /* Mapped address */ @@ -115,6 +119,8 @@ struct sdhci_host { #define SDHCI_NEEDS_RETUNING (1<<5) /* Host needs retuning */ #define SDHCI_AUTO_CMD12 (1<<6) /* Auto CMD12 support */ #define SDHCI_AUTO_CMD23 (1<<7) /* Auto CMD23 support */ +#define SDHCI_PV_ENABLED (1<<8) /* Preset value enabled */ +#define SDHCI_SDIO_IRQ_ENABLED (1<<9) /* SDIO irq enabled */ unsigned int version; /* SDHCI spec. version */ @@ -125,6 +131,8 @@ struct sdhci_host { unsigned int clock; /* Current clock (MHz) */ u8 pwr; /* Current voltage */ + bool runtime_suspended; /* Host is runtime suspended */ + struct mmc_request *mrq; /* Current request */ struct mmc_command *cmd; /* Current command */ struct mmc_data *data; /* Current data request */ -- cgit v1.2.3 From b23cf0bd55b0c6b703982446f679e00d6d929524 Mon Sep 17 00:00:00 2001 From: Seungwon Jeon Date: Fri, 23 Sep 2011 14:15:29 +0900 Subject: mmc: core: Add default timeout value for CMD6 EXT_CSD[248] includes the default maximum timeout for CMD6. This field is added at eMMC4.5 Spec. And it can be used for default timeout except for some operations which don't define the timeout (i.e. background operation, sanitize, flush cache) in eMMC4.5 Spec. Signed-off-by: Seungwon Jeon Signed-off-by: Jaehoon Chung Signed-off-by: Chris Ball --- drivers/mmc/core/mmc.c | 16 ++++++++++++---- include/linux/mmc/card.h | 1 + include/linux/mmc/mmc.h | 1 + 3 files changed, 14 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 2a4c9a4d3c00..7dde373d1439 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -458,6 +458,12 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) else card->erased_byte = 0x0; + if (card->ext_csd.rev >= 6) + card->ext_csd.generic_cmd6_time = 10 * + ext_csd[EXT_CSD_GENERIC_CMD6_TIME]; + else + card->ext_csd.generic_cmd6_time = 0; + out: return err; } @@ -801,7 +807,8 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, */ if (card->ext_csd.enhanced_area_en) { err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, - EXT_CSD_ERASE_GROUP_DEF, 1, 0); + EXT_CSD_ERASE_GROUP_DEF, 1, + card->ext_csd.generic_cmd6_time); if (err && err != -EBADMSG) goto free_card; @@ -844,7 +851,8 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, if ((card->ext_csd.hs_max_dtr != 0) && (host->caps & MMC_CAP_MMC_HIGHSPEED)) { err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, - EXT_CSD_HS_TIMING, 1, 0); + EXT_CSD_HS_TIMING, 1, + card->ext_csd.generic_cmd6_time); if (err && err != -EBADMSG) goto free_card; @@ -924,7 +932,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BUS_WIDTH, ext_csd_bits[idx][0], - 0); + card->ext_csd.generic_cmd6_time); if (!err) { mmc_set_bus_width(card->host, bus_width); @@ -955,7 +963,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BUS_WIDTH, ext_csd_bits[idx][1], - 0); + card->ext_csd.generic_cmd6_time); } if (err) { printk(KERN_WARNING "%s: switch to bus width %d ddr %d " diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 92762865f7e0..2fcd24ccd38c 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -54,6 +54,7 @@ struct mmc_ext_csd { u8 rst_n_function; unsigned int part_time; /* Units: ms */ unsigned int sa_timeout; /* Units: 100ns */ + unsigned int generic_cmd6_time; /* Units: 10ms */ unsigned int hs_max_dtr; unsigned int sectors; unsigned int card_type; diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index ea558eb44c79..cd63c2dc95cc 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -305,6 +305,7 @@ struct _mmc_csd { #define EXT_CSD_PWR_CL_DDR_52_195 238 /* RO */ #define EXT_CSD_PWR_CL_DDR_52_360 239 /* RO */ #define EXT_CSD_POWER_OFF_LONG_TIME 247 /* RO */ +#define EXT_CSD_GENERIC_CMD6_TIME 248 /* RO */ /* * EXT_CSD field definitions -- cgit v1.2.3 From bec8726abc72bf30d2743a722aa37cd69e7a0580 Mon Sep 17 00:00:00 2001 From: Girish K S Date: Thu, 13 Oct 2011 12:04:16 +0530 Subject: mmc: core: Add Power Off Notify Feature eMMC 4.5 This patch adds support for the power off notify feature, available in eMMC 4.5 devices. If the host has support for this feature, then the mmc core will notify the device by setting the POWER_OFF_NOTIFICATION byte in the extended csd register with a value of 1 (POWER_ON). For suspend mode short timeout is used, whereas for the normal poweroff long timeout is used. Signed-off-by: Girish K S Signed-off-by: Jaehoon Chung Signed-off-by: Chris Ball --- drivers/mmc/core/core.c | 34 ++++++++++++++++++++++++++++++++++ drivers/mmc/core/mmc.c | 23 +++++++++++++++++++++-- drivers/mmc/host/sdhci.c | 9 +++++++++ include/linux/mmc/card.h | 6 ++++++ include/linux/mmc/host.h | 5 +++++ include/linux/mmc/mmc.h | 6 ++++++ 6 files changed, 81 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 61d7730bc8b2..a3c4e0fe9434 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1212,11 +1212,43 @@ static void mmc_power_up(struct mmc_host *host) void mmc_power_off(struct mmc_host *host) { + struct mmc_card *card; + unsigned int notify_type; + unsigned int timeout; + int err; + mmc_host_clk_hold(host); + card = host->card; host->ios.clock = 0; host->ios.vdd = 0; + if (card && mmc_card_mmc(card) && + (card->poweroff_notify_state == MMC_POWERED_ON)) { + + if (host->power_notify_type == MMC_HOST_PW_NOTIFY_SHORT) { + notify_type = EXT_CSD_POWER_OFF_SHORT; + timeout = card->ext_csd.generic_cmd6_time; + card->poweroff_notify_state = MMC_POWEROFF_SHORT; + } else { + notify_type = EXT_CSD_POWER_OFF_LONG; + timeout = card->ext_csd.power_off_longtime; + card->poweroff_notify_state = MMC_POWEROFF_LONG; + } + + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_POWER_OFF_NOTIFICATION, + notify_type, timeout); + + if (err && err != -EBADMSG) + pr_err("Device failed to respond within %d poweroff " + "time. Forcefully powering down the device\n", + timeout); + + /* Set the card state to no notification after the poweroff */ + card->poweroff_notify_state = MMC_NO_POWER_NOTIFICATION; + } + /* * Reset ocr mask to be the highest possible voltage supported for * this mmc host. This value will be used at next power up. @@ -2208,6 +2240,7 @@ int mmc_pm_notify(struct notifier_block *notify_block, spin_lock_irqsave(&host->lock, flags); host->rescan_disable = 1; + host->power_notify_type = MMC_HOST_PW_NOTIFY_SHORT; spin_unlock_irqrestore(&host->lock, flags); cancel_delayed_work_sync(&host->detect); @@ -2231,6 +2264,7 @@ int mmc_pm_notify(struct notifier_block *notify_block, spin_lock_irqsave(&host->lock, flags); host->rescan_disable = 0; + host->power_notify_type = MMC_HOST_PW_NOTIFY_LONG; spin_unlock_irqrestore(&host->lock, flags); mmc_detect_change(host, 0); diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 4e869d371a03..f8ea9387d75c 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -458,10 +458,12 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) else card->erased_byte = 0x0; - if (card->ext_csd.rev >= 6) + if (card->ext_csd.rev >= 6) { card->ext_csd.generic_cmd6_time = 10 * ext_csd[EXT_CSD_GENERIC_CMD6_TIME]; - else + card->ext_csd.power_off_longtime = 10 * + ext_csd[EXT_CSD_POWER_OFF_LONG_TIME]; + } else card->ext_csd.generic_cmd6_time = 0; out: @@ -845,6 +847,23 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, goto free_card; } + /* + * If the host supports the power_off_notify capability then + * set the notification byte in the ext_csd register of device + */ + if ((host->caps2 & MMC_CAP2_POWEROFF_NOTIFY) && + (card->poweroff_notify_state == MMC_NO_POWER_NOTIFICATION)) { + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_POWER_OFF_NOTIFICATION, + EXT_CSD_POWER_ON, + card->ext_csd.generic_cmd6_time); + if (err && err != -EBADMSG) + goto free_card; + } + + if (!err) + card->poweroff_notify_state = MMC_POWERED_ON; + /* * Activate high speed (if supported) */ diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 2cc3ffa7d766..6d8eea323541 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -2739,6 +2739,15 @@ int sdhci_add_host(struct sdhci_host *host) if (caps[1] & SDHCI_DRIVER_TYPE_D) mmc->caps |= MMC_CAP_DRIVER_TYPE_D; + /* + * If Power Off Notify capability is enabled by the host, + * set notify to short power off notify timeout value. + */ + if (mmc->caps2 & MMC_CAP2_POWEROFF_NOTIFY) + mmc->power_notify_type = MMC_HOST_PW_NOTIFY_SHORT; + else + mmc->power_notify_type = MMC_HOST_PW_NOTIFY_NONE; + /* Initial value for re-tuning timer count */ host->tuning_count = (caps[1] & SDHCI_RETUNING_TIMER_COUNT_MASK) >> SDHCI_RETUNING_TIMER_COUNT_SHIFT; diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 2fcd24ccd38c..711c3f8bfabd 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -55,6 +55,7 @@ struct mmc_ext_csd { unsigned int part_time; /* Units: ms */ unsigned int sa_timeout; /* Units: 100ns */ unsigned int generic_cmd6_time; /* Units: 10ms */ + unsigned int power_off_longtime; /* Units: ms */ unsigned int hs_max_dtr; unsigned int sectors; unsigned int card_type; @@ -209,6 +210,11 @@ struct mmc_card { #define MMC_QUIRK_BLK_NO_CMD23 (1<<7) /* Avoid CMD23 for regular multiblock */ #define MMC_QUIRK_BROKEN_BYTE_MODE_512 (1<<8) /* Avoid sending 512 bytes in */ /* byte mode */ + unsigned int poweroff_notify_state; /* eMMC4.5 notify feature */ +#define MMC_NO_POWER_NOTIFICATION 0 +#define MMC_POWERED_ON 1 +#define MMC_POWEROFF_SHORT 2 +#define MMC_POWEROFF_LONG 3 unsigned int erase_size; /* erase size in sectors */ unsigned int erase_shift; /* if erase unit is power 2 */ diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index aed5bc7245f7..cd10208d9a06 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -239,8 +239,13 @@ struct mmc_host { unsigned int caps2; /* More host capabilities */ #define MMC_CAP2_BOOTPART_NOACC (1 << 0) /* Boot partition no access */ +#define MMC_CAP2_POWEROFF_NOTIFY (1 << 2) /* Notify poweroff supported */ mmc_pm_flag_t pm_caps; /* supported pm features */ + unsigned int power_notify_type; +#define MMC_HOST_PW_NOTIFY_NONE 0 +#define MMC_HOST_PW_NOTIFY_SHORT 1 +#define MMC_HOST_PW_NOTIFY_LONG 2 #ifdef CONFIG_MMC_CLKGATE int clk_requests; /* internal reference counter */ diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index cd63c2dc95cc..02e9e3de9609 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -270,6 +270,7 @@ struct _mmc_csd { * EXT_CSD fields */ +#define EXT_CSD_POWER_OFF_NOTIFICATION 34 /* R/W */ #define EXT_CSD_GP_SIZE_MULT 143 /* R/W */ #define EXT_CSD_PARTITION_ATTRIBUTE 156 /* R/W */ #define EXT_CSD_PARTITION_SUPPORT 160 /* RO */ @@ -346,6 +347,11 @@ struct _mmc_csd { #define EXT_CSD_RST_N_EN_MASK 0x3 #define EXT_CSD_RST_N_ENABLED 1 /* RST_n is enabled on card */ +#define EXT_CSD_NO_POWER_NOTIFICATION 0 +#define EXT_CSD_POWER_ON 1 +#define EXT_CSD_POWER_OFF_SHORT 2 +#define EXT_CSD_POWER_OFF_LONG 3 + #define EXT_CSD_PWR_CL_8BIT_MASK 0xF0 /* 8 bit PWR CLS */ #define EXT_CSD_PWR_CL_4BIT_MASK 0x0F /* 8 bit PWR CLS */ #define EXT_CSD_PWR_CL_8BIT_SHIFT 4 -- cgit v1.2.3 From 4e0a5adf46ee7810af2e1b7e4e8c2a298652618e Mon Sep 17 00:00:00 2001 From: Jaehoon Chung Date: Mon, 17 Oct 2011 19:36:23 +0900 Subject: mmc: dw_mmc: modify DATA register offset In dw_mmc 2.40a spec, Data register's offset is changed. Before we used Data register offset 0x100. but if somebody uses a 2.40a controller, we must use 0x200 for Data register. This patch adds a version-id checking point and uses SDMMC_DATA(x) instead of SDMMC_DATA. It assumes 2.40a is the latest version. Signed-off-by: Jaehoon Chung Signed-off-by: Kyungmin Park Acked-by: James Hogan Signed-off-by: Chris Ball --- drivers/mmc/host/dw_mmc.c | 66 +++++++++++++++++++++++++++++++--------------- drivers/mmc/host/dw_mmc.h | 13 ++++++++- include/linux/mmc/dw_mmc.h | 4 +++ 3 files changed, 61 insertions(+), 22 deletions(-) (limited to 'include') diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c index 701f14e8b54b..3aaeb0841914 100644 --- a/drivers/mmc/host/dw_mmc.c +++ b/drivers/mmc/host/dw_mmc.c @@ -1043,7 +1043,8 @@ static void dw_mci_push_data16(struct dw_mci *host, void *buf, int cnt) buf += len; cnt -= len; if (!sg_next(host->sg) || host->part_buf_count == 2) { - mci_writew(host, DATA, host->part_buf16); + mci_writew(host, DATA(host->data_offset), + host->part_buf16); host->part_buf_count = 0; } } @@ -1060,21 +1061,23 @@ static void dw_mci_push_data16(struct dw_mci *host, void *buf, int cnt) cnt -= len; /* push data from aligned buffer into fifo */ for (i = 0; i < items; ++i) - mci_writew(host, DATA, aligned_buf[i]); + mci_writew(host, DATA(host->data_offset), + aligned_buf[i]); } } else #endif { u16 *pdata = buf; for (; cnt >= 2; cnt -= 2) - mci_writew(host, DATA, *pdata++); + mci_writew(host, DATA(host->data_offset), *pdata++); buf = pdata; } /* put anything remaining in the part_buf */ if (cnt) { dw_mci_set_part_bytes(host, buf, cnt); if (!sg_next(host->sg)) - mci_writew(host, DATA, host->part_buf16); + mci_writew(host, DATA(host->data_offset), + host->part_buf16); } } @@ -1089,7 +1092,8 @@ static void dw_mci_pull_data16(struct dw_mci *host, void *buf, int cnt) int items = len >> 1; int i; for (i = 0; i < items; ++i) - aligned_buf[i] = mci_readw(host, DATA); + aligned_buf[i] = mci_readw(host, + DATA(host->data_offset)); /* memcpy from aligned buffer into output buffer */ memcpy(buf, aligned_buf, len); buf += len; @@ -1100,11 +1104,11 @@ static void dw_mci_pull_data16(struct dw_mci *host, void *buf, int cnt) { u16 *pdata = buf; for (; cnt >= 2; cnt -= 2) - *pdata++ = mci_readw(host, DATA); + *pdata++ = mci_readw(host, DATA(host->data_offset)); buf = pdata; } if (cnt) { - host->part_buf16 = mci_readw(host, DATA); + host->part_buf16 = mci_readw(host, DATA(host->data_offset)); dw_mci_pull_final_bytes(host, buf, cnt); } } @@ -1117,7 +1121,8 @@ static void dw_mci_push_data32(struct dw_mci *host, void *buf, int cnt) buf += len; cnt -= len; if (!sg_next(host->sg) || host->part_buf_count == 4) { - mci_writel(host, DATA, host->part_buf32); + mci_writel(host, DATA(host->data_offset), + host->part_buf32); host->part_buf_count = 0; } } @@ -1134,21 +1139,23 @@ static void dw_mci_push_data32(struct dw_mci *host, void *buf, int cnt) cnt -= len; /* push data from aligned buffer into fifo */ for (i = 0; i < items; ++i) - mci_writel(host, DATA, aligned_buf[i]); + mci_writel(host, DATA(host->data_offset), + aligned_buf[i]); } } else #endif { u32 *pdata = buf; for (; cnt >= 4; cnt -= 4) - mci_writel(host, DATA, *pdata++); + mci_writel(host, DATA(host->data_offset), *pdata++); buf = pdata; } /* put anything remaining in the part_buf */ if (cnt) { dw_mci_set_part_bytes(host, buf, cnt); if (!sg_next(host->sg)) - mci_writel(host, DATA, host->part_buf32); + mci_writel(host, DATA(host->data_offset), + host->part_buf32); } } @@ -1163,7 +1170,8 @@ static void dw_mci_pull_data32(struct dw_mci *host, void *buf, int cnt) int items = len >> 2; int i; for (i = 0; i < items; ++i) - aligned_buf[i] = mci_readl(host, DATA); + aligned_buf[i] = mci_readl(host, + DATA(host->data_offset)); /* memcpy from aligned buffer into output buffer */ memcpy(buf, aligned_buf, len); buf += len; @@ -1174,11 +1182,11 @@ static void dw_mci_pull_data32(struct dw_mci *host, void *buf, int cnt) { u32 *pdata = buf; for (; cnt >= 4; cnt -= 4) - *pdata++ = mci_readl(host, DATA); + *pdata++ = mci_readl(host, DATA(host->data_offset)); buf = pdata; } if (cnt) { - host->part_buf32 = mci_readl(host, DATA); + host->part_buf32 = mci_readl(host, DATA(host->data_offset)); dw_mci_pull_final_bytes(host, buf, cnt); } } @@ -1191,7 +1199,8 @@ static void dw_mci_push_data64(struct dw_mci *host, void *buf, int cnt) buf += len; cnt -= len; if (!sg_next(host->sg) || host->part_buf_count == 8) { - mci_writew(host, DATA, host->part_buf); + mci_writew(host, DATA(host->data_offset), + host->part_buf); host->part_buf_count = 0; } } @@ -1208,21 +1217,23 @@ static void dw_mci_push_data64(struct dw_mci *host, void *buf, int cnt) cnt -= len; /* push data from aligned buffer into fifo */ for (i = 0; i < items; ++i) - mci_writeq(host, DATA, aligned_buf[i]); + mci_writeq(host, DATA(host->data_offset), + aligned_buf[i]); } } else #endif { u64 *pdata = buf; for (; cnt >= 8; cnt -= 8) - mci_writeq(host, DATA, *pdata++); + mci_writeq(host, DATA(host->data_offset), *pdata++); buf = pdata; } /* put anything remaining in the part_buf */ if (cnt) { dw_mci_set_part_bytes(host, buf, cnt); if (!sg_next(host->sg)) - mci_writeq(host, DATA, host->part_buf); + mci_writeq(host, DATA(host->data_offset), + host->part_buf); } } @@ -1237,7 +1248,8 @@ static void dw_mci_pull_data64(struct dw_mci *host, void *buf, int cnt) int items = len >> 3; int i; for (i = 0; i < items; ++i) - aligned_buf[i] = mci_readq(host, DATA); + aligned_buf[i] = mci_readq(host, + DATA(host->data_offset)); /* memcpy from aligned buffer into output buffer */ memcpy(buf, aligned_buf, len); buf += len; @@ -1248,11 +1260,11 @@ static void dw_mci_pull_data64(struct dw_mci *host, void *buf, int cnt) { u64 *pdata = buf; for (; cnt >= 8; cnt -= 8) - *pdata++ = mci_readq(host, DATA); + *pdata++ = mci_readq(host, DATA(host->data_offset)); buf = pdata; } if (cnt) { - host->part_buf = mci_readq(host, DATA); + host->part_buf = mci_readq(host, DATA(host->data_offset)); dw_mci_pull_final_bytes(host, buf, cnt); } } @@ -1951,6 +1963,18 @@ static int dw_mci_probe(struct platform_device *pdev) } } + /* + * In 2.40a spec, Data offset is changed. + * Need to check the version-id and set data-offset for DATA register. + */ + host->verid = SDMMC_GET_VERID(mci_readl(host, VERID)); + dev_info(&pdev->dev, "Version ID is %04x\n", host->verid); + + if (host->verid < DW_MMC_240A) + host->data_offset = DATA_OFFSET; + else + host->data_offset = DATA_240A_OFFSET; + /* * Enable interrupts for command done, data over, data empty, card det, * receive ready and error such as transmit, receive timeout, crc error diff --git a/drivers/mmc/host/dw_mmc.h b/drivers/mmc/host/dw_mmc.h index bfa3c1cd05ac..72c071f6e001 100644 --- a/drivers/mmc/host/dw_mmc.h +++ b/drivers/mmc/host/dw_mmc.h @@ -14,6 +14,8 @@ #ifndef _DW_MMC_H_ #define _DW_MMC_H_ +#define DW_MMC_240A 0x240a + #define SDMMC_CTRL 0x000 #define SDMMC_PWREN 0x004 #define SDMMC_CLKDIV 0x008 @@ -51,7 +53,14 @@ #define SDMMC_IDINTEN 0x090 #define SDMMC_DSCADDR 0x094 #define SDMMC_BUFADDR 0x098 -#define SDMMC_DATA 0x100 +#define SDMMC_DATA(x) (x) + +/* + * Data offset is difference according to Version + * Lower than 2.40a : data register offest is 0x100 + */ +#define DATA_OFFSET 0x100 +#define DATA_240A_OFFSET 0x200 /* shift bit field */ #define _SBF(f, v) ((v) << (f)) @@ -130,6 +139,8 @@ #define SDMMC_IDMAC_ENABLE BIT(7) #define SDMMC_IDMAC_FB BIT(1) #define SDMMC_IDMAC_SWRESET BIT(0) +/* Version ID register define */ +#define SDMMC_GET_VERID(x) ((x) & 0xFFFF) /* Register access macros */ #define mci_readl(dev, reg) \ diff --git a/include/linux/mmc/dw_mmc.h b/include/linux/mmc/dw_mmc.h index 6b46819705d1..6dc9b80568a0 100644 --- a/include/linux/mmc/dw_mmc.h +++ b/include/linux/mmc/dw_mmc.h @@ -72,6 +72,8 @@ struct mmc_data; * rate and timeout calculations. * @current_speed: Configured rate of the controller. * @num_slots: Number of slots available. + * @verid: Denote Version ID. + * @data_offset: Set the offset of DATA register according to VERID. * @pdev: Platform device associated with the MMC controller. * @pdata: Platform data associated with the MMC controller. * @slot: Slots sharing this MMC controller. @@ -147,6 +149,8 @@ struct dw_mci { u32 current_speed; u32 num_slots; u32 fifoth_val; + u16 verid; + u16 data_offset; struct platform_device *pdev; struct dw_mci_board *pdata; struct dw_mci_slot *slot[MAX_MCI_SLOTS]; -- cgit v1.2.3 From d9ddd62943ee07a75d0428ffcf52f1a747a28c39 Mon Sep 17 00:00:00 2001 From: Kyungmin Park Date: Fri, 14 Oct 2011 14:15:48 +0900 Subject: mmc: core: mmc sanitize feature support for v4.5 In the v4.5, there's no secure erase & trim support. Instead it supports the sanitize feature. Signed-off-by: Kyungmin Park Signed-off-by: Jaehoon Chung Signed-off-by: Chris Ball --- drivers/mmc/card/block.c | 9 ++++++++- drivers/mmc/card/queue.c | 2 +- drivers/mmc/core/core.c | 8 ++++++++ include/linux/mmc/core.h | 1 + include/linux/mmc/mmc.h | 2 ++ 5 files changed, 20 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 049445eb4f74..e85816e1634a 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -792,11 +792,18 @@ static int mmc_blk_issue_secdiscard_rq(struct mmc_queue *mq, unsigned int from, nr, arg; int err = 0, type = MMC_BLK_SECDISCARD; - if (!mmc_can_secure_erase_trim(card)) { + if (!(mmc_can_secure_erase_trim(card) || mmc_can_sanitize(card))) { err = -EOPNOTSUPP; goto out; } + /* The sanitize operation is supported at v4.5 only */ + if (mmc_can_sanitize(card)) { + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_SANITIZE_START, 1, 0); + goto out; + } + from = blk_rq_pos(req); nr = blk_rq_sectors(req); diff --git a/drivers/mmc/card/queue.c b/drivers/mmc/card/queue.c index fed290ecc242..dcad59cbfef1 100644 --- a/drivers/mmc/card/queue.c +++ b/drivers/mmc/card/queue.c @@ -140,7 +140,7 @@ static void mmc_queue_setup_discard(struct request_queue *q, /* granularity must not be greater than max. discard */ if (card->pref_erase > max_discard) q->limits.discard_granularity = 0; - if (mmc_can_secure_erase_trim(card)) + if (mmc_can_secure_erase_trim(card) || mmc_can_sanitize(card)) queue_flag_set_unlocked(QUEUE_FLAG_SECDISCARD, q); } diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index a3c4e0fe9434..d9836e5a4e59 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1713,6 +1713,14 @@ int mmc_can_trim(struct mmc_card *card) } EXPORT_SYMBOL(mmc_can_trim); +int mmc_can_sanitize(struct mmc_card *card) +{ + if (card->ext_csd.sec_feature_support & EXT_CSD_SEC_SANITIZE) + return 1; + return 0; +} +EXPORT_SYMBOL(mmc_can_sanitize); + int mmc_can_secure_erase_trim(struct mmc_card *card) { if (card->ext_csd.sec_feature_support & EXT_CSD_SEC_ER_EN) diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 56e5625b6f41..9585835cbc42 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -156,6 +156,7 @@ extern int mmc_erase(struct mmc_card *card, unsigned int from, unsigned int nr, unsigned int arg); extern int mmc_can_erase(struct mmc_card *card); extern int mmc_can_trim(struct mmc_card *card); +extern int mmc_can_sanitize(struct mmc_card *card); extern int mmc_can_secure_erase_trim(struct mmc_card *card); extern int mmc_erase_group_aligned(struct mmc_card *card, unsigned int from, unsigned int nr); diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index 02e9e3de9609..8a05a21fc7c3 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -275,6 +275,7 @@ struct _mmc_csd { #define EXT_CSD_PARTITION_ATTRIBUTE 156 /* R/W */ #define EXT_CSD_PARTITION_SUPPORT 160 /* RO */ #define EXT_CSD_RST_N_FUNCTION 162 /* R/W */ +#define EXT_CSD_SANITIZE_START 165 /* W */ #define EXT_CSD_WR_REL_PARAM 166 /* RO */ #define EXT_CSD_ERASE_GROUP_DEF 175 /* R/W */ #define EXT_CSD_PART_CONFIG 179 /* R/W */ @@ -343,6 +344,7 @@ struct _mmc_csd { #define EXT_CSD_SEC_ER_EN BIT(0) #define EXT_CSD_SEC_BD_BLK_EN BIT(2) #define EXT_CSD_SEC_GB_CL_EN BIT(4) +#define EXT_CSD_SEC_SANITIZE BIT(6) /* v4.5 only */ #define EXT_CSD_RST_N_EN_MASK 0x3 #define EXT_CSD_RST_N_ENABLED 1 /* RST_n is enabled on card */ -- cgit v1.2.3 From b3bf915308ca2b50f3beec6cc824083870f0f4b5 Mon Sep 17 00:00:00 2001 From: Kyungmin Park Date: Tue, 18 Oct 2011 09:34:04 +0900 Subject: mmc: core: new discard feature support at eMMC v4.5 MMC v4.5 supports the DISCARD feature (CMD38). It's different from trim and there's no check bit. Currently it's only supported at v4.5. Signed-off-by: Kyungmin Park Signed-off-by: Jaehoon Chung Signed-off-by: Chris Ball --- drivers/mmc/card/block.c | 4 +++- drivers/mmc/core/core.c | 14 ++++++++++++++ drivers/mmc/core/mmc.c | 4 ++++ include/linux/mmc/card.h | 3 +++ include/linux/mmc/core.h | 2 ++ 5 files changed, 26 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index e85816e1634a..370472797fff 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -756,7 +756,9 @@ static int mmc_blk_issue_discard_rq(struct mmc_queue *mq, struct request *req) from = blk_rq_pos(req); nr = blk_rq_sectors(req); - if (mmc_can_trim(card)) + if (mmc_can_discard(card)) + arg = MMC_DISCARD_ARG; + else if (mmc_can_trim(card)) arg = MMC_TRIM_ARG; else arg = MMC_ERASE_ARG; diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index d9836e5a4e59..772de2cdfd1d 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -1709,10 +1709,24 @@ int mmc_can_trim(struct mmc_card *card) { if (card->ext_csd.sec_feature_support & EXT_CSD_SEC_GB_CL_EN) return 1; + if (mmc_can_discard(card)) + return 1; return 0; } EXPORT_SYMBOL(mmc_can_trim); +int mmc_can_discard(struct mmc_card *card) +{ + /* + * As there's no way to detect the discard support bit at v4.5 + * use the s/w feature support filed. + */ + if (card->ext_csd.feature_support & MMC_DISCARD_FEATURE) + return 1; + return 0; +} +EXPORT_SYMBOL(mmc_can_discard); + int mmc_can_sanitize(struct mmc_card *card) { if (card->ext_csd.sec_feature_support & EXT_CSD_SEC_SANITIZE) diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index f8ea9387d75c..54088768776c 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -452,6 +452,10 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) card->ext_csd.rst_n_function = ext_csd[EXT_CSD_RST_N_FUNCTION]; } + /* eMMC v4.5 or later */ + if (card->ext_csd.rev >= 6) + card->ext_csd.feature_support |= MMC_DISCARD_FEATURE; + card->ext_csd.raw_erased_mem_count = ext_csd[EXT_CSD_ERASED_MEM_CONT]; if (ext_csd[EXT_CSD_ERASED_MEM_CONT]) card->erased_byte = 0xFF; diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 711c3f8bfabd..551378508784 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -80,6 +80,9 @@ struct mmc_ext_csd { u8 raw_sec_feature_support;/* 231 */ u8 raw_trim_mult; /* 232 */ u8 raw_sectors[4]; /* 212 - 4 bytes */ + + unsigned int feature_support; +#define MMC_DISCARD_FEATURE BIT(0) /* CMD38 feature */ }; struct sd_scr { diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 9585835cbc42..e918120235bd 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -146,6 +146,7 @@ extern int mmc_switch(struct mmc_card *, u8, u8, u8, unsigned int); #define MMC_ERASE_ARG 0x00000000 #define MMC_SECURE_ERASE_ARG 0x80000000 #define MMC_TRIM_ARG 0x00000001 +#define MMC_DISCARD_ARG 0x00000003 #define MMC_SECURE_TRIM1_ARG 0x80000001 #define MMC_SECURE_TRIM2_ARG 0x80008000 @@ -156,6 +157,7 @@ extern int mmc_erase(struct mmc_card *card, unsigned int from, unsigned int nr, unsigned int arg); extern int mmc_can_erase(struct mmc_card *card); extern int mmc_can_trim(struct mmc_card *card); +extern int mmc_can_discard(struct mmc_card *card); extern int mmc_can_sanitize(struct mmc_card *card); extern int mmc_can_secure_erase_trim(struct mmc_card *card); extern int mmc_erase_group_aligned(struct mmc_card *card, unsigned int from, -- cgit v1.2.3 From 881d1c25f765938a95def5afe39486ce39f9fc96 Mon Sep 17 00:00:00 2001 From: Seungwon Jeon Date: Fri, 14 Oct 2011 14:03:21 +0900 Subject: mmc: core: Add cache control for eMMC4.5 device This patch adds cache feature of eMMC4.5 Spec. If device supports cache capability, host can utilize some specific operations. Signed-off-by: Seungwon Jeon Signed-off-by: Jaehoon Chung Signed-off-by: Chris Ball --- drivers/mmc/card/block.c | 14 ++++++----- drivers/mmc/core/core.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ drivers/mmc/core/mmc.c | 23 ++++++++++++++++++ include/linux/mmc/card.h | 2 ++ include/linux/mmc/core.h | 2 ++ include/linux/mmc/host.h | 3 +++ include/linux/mmc/mmc.h | 3 +++ 7 files changed, 104 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 370472797fff..c0cb225bbb47 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -851,16 +851,18 @@ out: static int mmc_blk_issue_flush(struct mmc_queue *mq, struct request *req) { struct mmc_blk_data *md = mq->data; + struct mmc_card *card = md->queue.card; + int ret = 0; + + ret = mmc_flush_cache(card); + if (ret) + ret = -EIO; - /* - * No-op, only service this because we need REQ_FUA for reliable - * writes. - */ spin_lock_irq(&md->lock); - __blk_end_request_all(req, 0); + __blk_end_request_all(req, ret); spin_unlock_irq(&md->lock); - return 1; + return ret ? 0 : 1; } /* diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 772de2cdfd1d..235bb6a1f973 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -2158,6 +2158,65 @@ int mmc_card_can_sleep(struct mmc_host *host) } EXPORT_SYMBOL(mmc_card_can_sleep); +/* + * Flush the cache to the non-volatile storage. + */ +int mmc_flush_cache(struct mmc_card *card) +{ + struct mmc_host *host = card->host; + int err = 0; + + if (!(host->caps2 & MMC_CAP2_CACHE_CTRL)) + return err; + + if (mmc_card_mmc(card) && + (card->ext_csd.cache_size > 0) && + (card->ext_csd.cache_ctrl & 1)) { + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_FLUSH_CACHE, 1, 0); + if (err) + pr_err("%s: cache flush error %d\n", + mmc_hostname(card->host), err); + } + + return err; +} +EXPORT_SYMBOL(mmc_flush_cache); + +/* + * Turn the cache ON/OFF. + * Turning the cache OFF shall trigger flushing of the data + * to the non-volatile storage. + */ +int mmc_cache_ctrl(struct mmc_host *host, u8 enable) +{ + struct mmc_card *card = host->card; + int err = 0; + + if (!(host->caps2 & MMC_CAP2_CACHE_CTRL) || + mmc_card_is_removable(host)) + return err; + + if (card && mmc_card_mmc(card) && + (card->ext_csd.cache_size > 0)) { + enable = !!enable; + + if (card->ext_csd.cache_ctrl ^ enable) + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_CACHE_CTRL, enable, 0); + if (err) + pr_err("%s: cache %s error %d\n", + mmc_hostname(card->host), + enable ? "on" : "off", + err); + else + card->ext_csd.cache_ctrl = enable; + } + + return err; +} +EXPORT_SYMBOL(mmc_cache_ctrl); + #ifdef CONFIG_PM /** @@ -2172,6 +2231,9 @@ int mmc_suspend_host(struct mmc_host *host) cancel_delayed_work(&host->disable); cancel_delayed_work(&host->detect); mmc_flush_scheduled_work(); + err = mmc_cache_ctrl(host, 0); + if (err) + goto out; mmc_bus_get(host); if (host->bus_ops && !host->bus_dead) { @@ -2197,6 +2259,7 @@ int mmc_suspend_host(struct mmc_host *host) if (!err && !mmc_card_keep_power(host)) mmc_power_off(host); +out: return err; } diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 793901519208..de5900aa81cd 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -470,6 +470,12 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) } else card->ext_csd.generic_cmd6_time = 0; + card->ext_csd.cache_size = + ext_csd[EXT_CSD_CACHE_SIZE + 0] << 0 | + ext_csd[EXT_CSD_CACHE_SIZE + 1] << 8 | + ext_csd[EXT_CSD_CACHE_SIZE + 2] << 16 | + ext_csd[EXT_CSD_CACHE_SIZE + 3] << 24; + out: return err; } @@ -1020,6 +1026,23 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, } } + /* + * If cache size is higher than 0, this indicates + * the existence of cache and it can be turned on. + */ + if ((host->caps2 & MMC_CAP2_CACHE_CTRL) && + card->ext_csd.cache_size > 0) { + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_CACHE_CTRL, 1, 0); + if (err && err != -EBADMSG) + goto free_card; + + /* + * Only if no error, cache is turned on successfully. + */ + card->ext_csd.cache_ctrl = err ? 0 : 1; + } + if (!oldcard) host->card = card; diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 551378508784..32492b78aed6 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -51,6 +51,7 @@ struct mmc_ext_csd { u8 rel_sectors; u8 rel_param; u8 part_config; + u8 cache_ctrl; u8 rst_n_function; unsigned int part_time; /* Units: ms */ unsigned int sa_timeout; /* Units: 100ns */ @@ -67,6 +68,7 @@ struct mmc_ext_csd { bool enhanced_area_en; /* enable bit */ unsigned long long enhanced_area_offset; /* Units: Byte */ unsigned int enhanced_area_size; /* Units: KB */ + unsigned int cache_size; /* Units: KB */ u8 raw_partition_support; /* 160 */ u8 raw_erased_mem_count; /* 181 */ u8 raw_ext_csd_structure; /* 194 */ diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index e918120235bd..67bac374d122 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -177,6 +177,8 @@ extern void mmc_release_host(struct mmc_host *host); extern void mmc_do_release_host(struct mmc_host *host); extern int mmc_try_claim_host(struct mmc_host *host); +extern int mmc_flush_cache(struct mmc_card *); + /** * mmc_claim_host - exclusively claim a host * @host: mmc host to claim diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index cd10208d9a06..16e9338944e8 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -239,6 +239,7 @@ struct mmc_host { unsigned int caps2; /* More host capabilities */ #define MMC_CAP2_BOOTPART_NOACC (1 << 0) /* Boot partition no access */ +#define MMC_CAP2_CACHE_CTRL (1 << 1) /* Allow cache control */ #define MMC_CAP2_POWEROFF_NOTIFY (1 << 2) /* Notify poweroff supported */ mmc_pm_flag_t pm_caps; /* supported pm features */ @@ -349,6 +350,8 @@ extern int mmc_power_restore_host(struct mmc_host *host); extern void mmc_detect_change(struct mmc_host *, unsigned long delay); extern void mmc_request_done(struct mmc_host *, struct mmc_request *); +extern int mmc_cache_ctrl(struct mmc_host *, u8); + static inline void mmc_signal_sdio_irq(struct mmc_host *host) { host->ops->enable_sdio_irq(host, 0); diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index 8a05a21fc7c3..160e4c5bdae0 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -270,6 +270,8 @@ struct _mmc_csd { * EXT_CSD fields */ +#define EXT_CSD_FLUSH_CACHE 32 /* W */ +#define EXT_CSD_CACHE_CTRL 33 /* R/W */ #define EXT_CSD_POWER_OFF_NOTIFICATION 34 /* R/W */ #define EXT_CSD_GP_SIZE_MULT 143 /* R/W */ #define EXT_CSD_PARTITION_ATTRIBUTE 156 /* R/W */ @@ -308,6 +310,7 @@ struct _mmc_csd { #define EXT_CSD_PWR_CL_DDR_52_360 239 /* RO */ #define EXT_CSD_POWER_OFF_LONG_TIME 247 /* RO */ #define EXT_CSD_GENERIC_CMD6_TIME 248 /* RO */ +#define EXT_CSD_CACHE_SIZE 249 /* RO, 4 bytes */ /* * EXT_CSD field definitions -- cgit v1.2.3 From eb0d8f135b6730d6d0324a064664d121334290e7 Mon Sep 17 00:00:00 2001 From: Jaehoon Chung Date: Tue, 18 Oct 2011 01:26:42 -0400 Subject: mmc: core: support HPI send command HPI command is defined in eMMC4.41. This feature is important for eMMC4.5 devices. Signed-off-by: Jaehoon Chung Signed-off-by: Chris Ball --- drivers/mmc/core/core.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/mmc/core/mmc.c | 31 +++++++++++++++++++++++++ drivers/mmc/core/mmc_ops.c | 31 +++++++++++++++++++++++++ drivers/mmc/core/mmc_ops.h | 1 + include/linux/mmc/card.h | 4 ++++ include/linux/mmc/core.h | 1 + include/linux/mmc/mmc.h | 3 +++ 7 files changed, 128 insertions(+) (limited to 'include') diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index 235bb6a1f973..fe65bb377e25 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -379,6 +379,63 @@ void mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq) } EXPORT_SYMBOL(mmc_wait_for_req); +/** + * mmc_interrupt_hpi - Issue for High priority Interrupt + * @card: the MMC card associated with the HPI transfer + * + * Issued High Priority Interrupt, and check for card status + * util out-of prg-state. + */ +int mmc_interrupt_hpi(struct mmc_card *card) +{ + int err; + u32 status; + + BUG_ON(!card); + + if (!card->ext_csd.hpi_en) { + pr_info("%s: HPI enable bit unset\n", mmc_hostname(card->host)); + return 1; + } + + mmc_claim_host(card->host); + err = mmc_send_status(card, &status); + if (err) { + pr_err("%s: Get card status fail\n", mmc_hostname(card->host)); + goto out; + } + + /* + * If the card status is in PRG-state, we can send the HPI command. + */ + if (R1_CURRENT_STATE(status) == R1_STATE_PRG) { + do { + /* + * We don't know when the HPI command will finish + * processing, so we need to resend HPI until out + * of prg-state, and keep checking the card status + * with SEND_STATUS. If a timeout error occurs when + * sending the HPI command, we are already out of + * prg-state. + */ + err = mmc_send_hpi_cmd(card, &status); + if (err) + pr_debug("%s: abort HPI (%d error)\n", + mmc_hostname(card->host), err); + + err = mmc_send_status(card, &status); + if (err) + break; + } while (R1_CURRENT_STATE(status) == R1_STATE_PRG); + } else + pr_debug("%s: Left prg-state\n", mmc_hostname(card->host)); + +out: + mmc_release_host(card->host); + return err; +} +EXPORT_SYMBOL(mmc_interrupt_hpi); + /** * mmc_wait_for_cmd - start a command and wait for completion * @host: MMC host to start command diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index de5900aa81cd..fb5bf01dd1b2 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -448,6 +448,21 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) } if (card->ext_csd.rev >= 5) { + /* check whether the eMMC card supports HPI */ + if (ext_csd[EXT_CSD_HPI_FEATURES] & 0x1) { + card->ext_csd.hpi = 1; + if (ext_csd[EXT_CSD_HPI_FEATURES] & 0x2) + card->ext_csd.hpi_cmd = MMC_STOP_TRANSMISSION; + else + card->ext_csd.hpi_cmd = MMC_SEND_STATUS; + /* + * Indicate the maximum timeout to close + * a command interrupted by HPI + */ + card->ext_csd.out_of_int_time = + ext_csd[EXT_CSD_OUT_OF_INTERRUPT_TIME] * 10; + } + card->ext_csd.rel_param = ext_csd[EXT_CSD_WR_REL_PARAM]; card->ext_csd.rst_n_function = ext_csd[EXT_CSD_RST_N_FUNCTION]; } @@ -895,6 +910,22 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, } } + /* + * Enable HPI feature (if supported) + */ + if (card->ext_csd.hpi) { + err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_HPI_MGMT, 1, 0); + if (err && err != -EBADMSG) + goto free_card; + if (err) { + pr_warning("%s: Enabling HPI failed\n", + mmc_hostname(card->host)); + err = 0; + } else + card->ext_csd.hpi_en = 1; + } + /* * Compute bus speed. */ diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index 4e11d56b3f70..007863eea4fb 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -547,3 +547,34 @@ int mmc_bus_test(struct mmc_card *card, u8 bus_width) err = mmc_send_bus_test(card, card->host, MMC_BUS_TEST_R, width); return err; } + +int mmc_send_hpi_cmd(struct mmc_card *card, u32 *status) +{ + struct mmc_command cmd = {0}; + unsigned int opcode; + unsigned int flags; + int err; + + opcode = card->ext_csd.hpi_cmd; + if (opcode == MMC_STOP_TRANSMISSION) + flags = MMC_RSP_R1 | MMC_CMD_AC; + else if (opcode == MMC_SEND_STATUS) + flags = MMC_RSP_R1 | MMC_CMD_AC; + + cmd.opcode = opcode; + cmd.arg = card->rca << 16 | 1; + cmd.flags = flags; + cmd.cmd_timeout_ms = card->ext_csd.out_of_int_time; + + err = mmc_wait_for_cmd(card->host, &cmd, 0); + if (err) { + pr_warn("%s: error %d interrupting operation. " + "HPI command response %#x\n", mmc_hostname(card->host), + err, cmd.resp[0]); + return err; + } + if (status) + *status = cmd.resp[0]; + + return 0; +} diff --git a/drivers/mmc/core/mmc_ops.h b/drivers/mmc/core/mmc_ops.h index 9276946fa5b7..3dd8941c2980 100644 --- a/drivers/mmc/core/mmc_ops.h +++ b/drivers/mmc/core/mmc_ops.h @@ -26,6 +26,7 @@ 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); int mmc_card_sleepawake(struct mmc_host *host, int sleep); int mmc_bus_test(struct mmc_card *card, u8 bus_width); +int mmc_send_hpi_cmd(struct mmc_card *card, u32 *status); #endif diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 32492b78aed6..1684d92a8015 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -69,10 +69,14 @@ struct mmc_ext_csd { unsigned long long enhanced_area_offset; /* Units: Byte */ unsigned int enhanced_area_size; /* Units: KB */ unsigned int cache_size; /* Units: KB */ + bool hpi_en; /* HPI enablebit */ + bool hpi; /* HPI support bit */ + unsigned int hpi_cmd; /* cmd used as HPI */ u8 raw_partition_support; /* 160 */ u8 raw_erased_mem_count; /* 181 */ u8 raw_ext_csd_structure; /* 194 */ u8 raw_card_type; /* 196 */ + u8 out_of_int_time; /* 198 */ u8 raw_s_a_timeout; /* 217 */ u8 raw_hc_erase_gap_size; /* 221 */ u8 raw_erase_timeout_mult; /* 223 */ diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 67bac374d122..174a844a5dda 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -136,6 +136,7 @@ struct mmc_async_req; extern struct mmc_async_req *mmc_start_req(struct mmc_host *, struct mmc_async_req *, int *); +extern int mmc_interrupt_hpi(struct mmc_card *); extern void mmc_wait_for_req(struct mmc_host *, struct mmc_request *); extern int mmc_wait_for_cmd(struct mmc_host *, struct mmc_command *, int); extern int mmc_app_cmd(struct mmc_host *, struct mmc_card *); diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index 160e4c5bdae0..0e7135697d11 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -276,6 +276,7 @@ struct _mmc_csd { #define EXT_CSD_GP_SIZE_MULT 143 /* R/W */ #define EXT_CSD_PARTITION_ATTRIBUTE 156 /* R/W */ #define EXT_CSD_PARTITION_SUPPORT 160 /* RO */ +#define EXT_CSD_HPI_MGMT 161 /* R/W */ #define EXT_CSD_RST_N_FUNCTION 162 /* R/W */ #define EXT_CSD_SANITIZE_START 165 /* W */ #define EXT_CSD_WR_REL_PARAM 166 /* RO */ @@ -288,6 +289,7 @@ struct _mmc_csd { #define EXT_CSD_REV 192 /* RO */ #define EXT_CSD_STRUCTURE 194 /* RO */ #define EXT_CSD_CARD_TYPE 196 /* RO */ +#define EXT_CSD_OUT_OF_INTERRUPT_TIME 198 /* RO */ #define EXT_CSD_PART_SWITCH_TIME 199 /* RO */ #define EXT_CSD_PWR_CL_52_195 200 /* RO */ #define EXT_CSD_PWR_CL_26_195 201 /* RO */ @@ -311,6 +313,7 @@ struct _mmc_csd { #define EXT_CSD_POWER_OFF_LONG_TIME 247 /* RO */ #define EXT_CSD_GENERIC_CMD6_TIME 248 /* RO */ #define EXT_CSD_CACHE_SIZE 249 /* RO, 4 bytes */ +#define EXT_CSD_HPI_FEATURES 503 /* RO */ /* * EXT_CSD field definitions -- cgit v1.2.3 From b4625dab2c618eb87e177761dda3182b4cfaa604 Mon Sep 17 00:00:00 2001 From: Bing Zhao Date: Thu, 20 Oct 2011 19:16:32 -0700 Subject: mmc: recognise SDIO cards with SDIO_CCCR_REV 3.00 Table 6-2: CCCR bit Definitions, address 00h. Part E1 SDIO Simplified Specification Version 3.00, Feb. 25, 2011. This patch has been tested with Marvell WLAN device SD8797. Signed-off-by: Bing Zhao Signed-off-by: Chris Ball --- drivers/mmc/core/sdio.c | 2 +- include/linux/mmc/sdio.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index 925bab052b07..3ab565e32a6a 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -111,7 +111,7 @@ static int sdio_read_cccr(struct mmc_card *card) cccr_vsn = data & 0x0f; - if (cccr_vsn > SDIO_CCCR_REV_1_20) { + if (cccr_vsn > SDIO_CCCR_REV_3_00) { pr_err("%s: unrecognised CCCR structure version %d\n", mmc_hostname(card->host), cccr_vsn); return -EINVAL; diff --git a/include/linux/mmc/sdio.h b/include/linux/mmc/sdio.h index 2a2e9905a247..e0b1123497b9 100644 --- a/include/linux/mmc/sdio.h +++ b/include/linux/mmc/sdio.h @@ -72,11 +72,13 @@ #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_CCCR_REV_3_00 3 /* CCCR/FBR Version 3.00 */ #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_SDIO_REV_3_00 4 /* SDIO Spec Version 3.00 */ #define SDIO_CCCR_SD 0x01 -- cgit v1.2.3 From 2bf22b39823c1d173dda31111a4eb2ce36daaf39 Mon Sep 17 00:00:00 2001 From: Paul Walmsley Date: Thu, 6 Oct 2011 14:50:33 -0600 Subject: mmc: core: add workaround for controllers with broken multiblock reads Due to hardware bugs, some MMC host controllers don't support multiple-block reads[1]. To resolve, add a new MMC capability flag, MMC_CAP2_NO_MULTI_READ, which can be set by affected host controller drivers. When this capability is set, all reads will be issued one sector at a time. 1. See for example Advisory 2.1.1.128 "MMC: Multiple Block Read Operation Issue" in _OMAP3530/3525/3515/3503 Silicon Errata_ Revision F (October 2010) (SPRZ278F), available from http://focus.ti.com/lit/er/sprz278f/sprz278f.pdf Signed-off-by: Paul Walmsley Cc: Dave Hylands Tested-by: Steve Sakoman Signed-off-by: Chris Ball --- drivers/mmc/card/block.c | 21 ++++++++++++++------- include/linux/mmc/host.h | 1 + 2 files changed, 15 insertions(+), 7 deletions(-) (limited to 'include') diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index c0cb225bbb47..a1cb21f95302 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -1030,13 +1030,20 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq, if (brq->data.blocks > card->host->max_blk_count) brq->data.blocks = card->host->max_blk_count; - /* - * After a read error, we redo the request one sector at a time - * in order to accurately determine which sectors can be read - * successfully. - */ - if (disable_multi && brq->data.blocks > 1) - brq->data.blocks = 1; + if (brq->data.blocks > 1) { + /* + * After a read error, we redo the request one sector + * at a time in order to accurately determine which + * sectors can be read successfully. + */ + if (disable_multi) + brq->data.blocks = 1; + + /* Some controllers can't do multiblock reads due to hw bugs */ + if (card->host->caps2 & MMC_CAP2_NO_MULTI_READ && + rq_data_dir(req) == READ) + brq->data.blocks = 1; + } if (brq->data.blocks > 1 || do_rel_wr) { /* SPI multiblock writes terminate using a special diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index 16e9338944e8..a3ac9c48e5de 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -241,6 +241,7 @@ struct mmc_host { #define MMC_CAP2_BOOTPART_NOACC (1 << 0) /* Boot partition no access */ #define MMC_CAP2_CACHE_CTRL (1 << 1) /* Allow cache control */ #define MMC_CAP2_POWEROFF_NOTIFY (1 << 2) /* Notify poweroff supported */ +#define MMC_CAP2_NO_MULTI_READ (1 << 3) /* Multiblock reads don't work */ mmc_pm_flag_t pm_caps; /* supported pm features */ unsigned int power_notify_type; -- cgit v1.2.3 From a6029e1f75bb484c1f5bc68b6a8572e4024795bc Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Thu, 13 Oct 2011 00:43:14 +0900 Subject: mmc: fix compile error when CONFIG_BLOCK is not enabled 'DISK_NAME_LEN' is undeclared when CONFIG_BLOCK is disabled; its use was introduced via genhd.h by the general purpose partition patch. To fix, we just add our own MAX_MMC_PART_NAME_LEN macro instead of using DISK_NAME_LEN. Reported-by: Randy Dunlap Signed-off-by: Namjae Jeon Acked-by: Randy Dunlap Acked-by: Andrei Warkentin Signed-off-by: Chris Ball --- include/linux/mmc/card.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 1684d92a8015..415f2db414e1 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -12,7 +12,6 @@ #include #include -#include struct mmc_cid { unsigned int manfid; @@ -175,6 +174,7 @@ struct sdio_func_tuple; #define MMC_NUM_BOOT_PARTITION 2 #define MMC_NUM_GP_PARTITION 4 #define MMC_NUM_PHY_PARTITION 6 +#define MAX_MMC_PART_NAME_LEN 20 /* * MMC Physical partitions @@ -182,7 +182,7 @@ struct sdio_func_tuple; struct mmc_part { unsigned int size; /* partition size (in bytes) */ unsigned int part_cfg; /* partition type */ - char name[DISK_NAME_LEN]; + char name[MAX_MMC_PART_NAME_LEN]; bool force_ro; /* to make boot parts RO by default */ }; -- cgit v1.2.3