aboutsummaryrefslogtreecommitdiff
path: root/drivers/mmc
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mmc')
-rw-r--r--drivers/mmc/card/Kconfig7
-rw-r--r--drivers/mmc/card/block.c5
-rw-r--r--drivers/mmc/core/Makefile1
-rw-r--r--drivers/mmc/core/core.c131
-rw-r--r--drivers/mmc/core/mmc.c32
-rw-r--r--drivers/mmc/core/mmc_ffu.c522
-rw-r--r--drivers/mmc/host/sdhci.c14
-rw-r--r--drivers/mmc/host/sdhci.h1
8 files changed, 698 insertions, 15 deletions
diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig
index f67e5b2d..e00f99a7 100644
--- a/drivers/mmc/card/Kconfig
+++ b/drivers/mmc/card/Kconfig
@@ -88,3 +88,10 @@ config MMC_BLOCK_TEST
Currently used to test eMMC 4.5 features (packed commands, sanitize,
BKOPs).
+config MMC_FFU
+ bool "FFU SUPPORT"
+ depends on MMC != n
+ help
+ This is an option to run firmware update on eMMC 5.0.
+ Field firmware updates (FFU) enables features enhancment
+ in the field.
diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c
index db111eb2..198be4df 100644
--- a/drivers/mmc/card/block.c
+++ b/drivers/mmc/card/block.c
@@ -632,6 +632,11 @@ static int mmc_blk_ioctl_cmd(struct block_device *bdev,
goto cmd_done;
}
+ if (idata->ic.opcode == MMC_FFU_INVOKE_OP) {
+ err = mmc_ffu_invoke(card, idata->buf);
+ goto cmd_done;
+ }
+
cmd.opcode = idata->ic.opcode;
cmd.arg = idata->ic.arg;
cmd.flags = idata->ic.flags;
diff --git a/drivers/mmc/core/Makefile b/drivers/mmc/core/Makefile
index 38ed210c..f2fdfd4b 100644
--- a/drivers/mmc/core/Makefile
+++ b/drivers/mmc/core/Makefile
@@ -10,3 +10,4 @@ mmc_core-y := core.o bus.o host.o \
quirks.o slot-gpio.o
mmc_core-$(CONFIG_DEBUG_FS) += debugfs.o
+obj-$(CONFIG_MMC_FFU) += mmc_ffu.o
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index b07abf86..085715bf 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -3997,6 +3997,137 @@ int mmc_flush_cache(struct mmc_card *card)
}
EXPORT_SYMBOL(mmc_flush_cache);
+/*
+ * Fill in the mmc_request structure given a set of transfer parameters.
+ */
+void mmc_prepare_mrq(struct mmc_card *card,
+ struct mmc_request *mrq, struct scatterlist *sg, unsigned sg_len,
+ unsigned dev_addr, unsigned blocks, unsigned blksz, int write)
+{
+ BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);
+
+ if (blocks > 1) {
+ mrq->cmd->opcode = write ?
+ MMC_WRITE_MULTIPLE_BLOCK : MMC_READ_MULTIPLE_BLOCK;
+ } else {
+ mrq->cmd->opcode = write ?
+ MMC_WRITE_BLOCK : MMC_READ_SINGLE_BLOCK;
+ }
+
+ mrq->cmd->arg = dev_addr;
+ if (!mmc_card_blockaddr(card))
+ mrq->cmd->arg <<= 9;
+
+ mrq->cmd->flags = MMC_RSP_R1 | MMC_CMD_ADTC;
+
+ if (blocks == 1)
+ mrq->stop = NULL;
+ else {
+ mrq->stop->opcode = MMC_STOP_TRANSMISSION;
+ mrq->stop->arg = 0;
+ mrq->stop->flags = MMC_RSP_R1B | MMC_CMD_AC;
+ }
+
+ mrq->data->blksz = blksz;
+ mrq->data->blocks = blocks;
+ mrq->data->flags = write ? MMC_DATA_WRITE : MMC_DATA_READ;
+ mrq->data->sg = sg;
+ mrq->data->sg_len = sg_len;
+
+ mmc_set_data_timeout(mrq->data, card);
+}
+EXPORT_SYMBOL(mmc_prepare_mrq);
+
+static int mmc_busy(struct mmc_command *cmd)
+{
+ return !(cmd->resp[0] & R1_READY_FOR_DATA) ||
+ (R1_CURRENT_STATE(cmd->resp[0]) == R1_STATE_PRG);
+}
+
+/*
+ * Wait for the card to finish the busy state
+ */
+int mmc_wait_busy(struct mmc_card *card)
+{
+ int ret, busy = 0;
+ struct mmc_command cmd = {0};
+
+ memset(&cmd, 0, sizeof(struct mmc_command));
+ cmd.opcode = MMC_SEND_STATUS;
+ cmd.arg = card->rca << 16;
+ cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;
+
+ do {
+ ret = mmc_wait_for_cmd(card->host, &cmd, 0);
+ if (ret)
+ break;
+
+ if (!busy && mmc_busy(&cmd)) {
+ busy = 1;
+ if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY) {
+ pr_warn("%s: Warning: Host did not "
+ "wait for busy state to end.\n",
+ mmc_hostname(card->host));
+ }
+ }
+
+ } while (mmc_busy(&cmd));
+
+ return ret;
+}
+EXPORT_SYMBOL(mmc_wait_busy);
+
+int mmc_check_result(struct mmc_request *mrq)
+{
+ int ret;
+
+ BUG_ON(!mrq || !mrq->cmd || !mrq->data);
+
+ ret = 0;
+
+ if (!ret && mrq->cmd->error)
+ ret = mrq->cmd->error;
+ if (!ret && mrq->data->error)
+ ret = mrq->data->error;
+ if (!ret && mrq->stop && mrq->stop->error)
+ ret = mrq->stop->error;
+ if (!ret && mrq->data->bytes_xfered !=
+ mrq->data->blocks * mrq->data->blksz)
+ ret = -EPERM;
+
+ return ret;
+}
+EXPORT_SYMBOL(mmc_check_result);
+
+/*
+ * transfer with certain parameters
+ */
+int mmc_simple_transfer(struct mmc_card *card,
+ struct scatterlist *sg, unsigned sg_len, unsigned dev_addr,
+ unsigned blocks, unsigned blksz, int write)
+{
+ struct mmc_request mrq = {0};
+ struct mmc_command cmd = {0};
+ struct mmc_command stop = {0};
+ struct mmc_data data = {0};
+
+ mrq.cmd = &cmd;
+ mrq.data = &data;
+ mrq.stop = &stop;
+
+ mmc_prepare_mrq(card, &mrq, sg, sg_len, dev_addr,
+ blocks, blksz, write);
+
+ mmc_wait_for_req(card->host, &mrq);
+
+ mmc_wait_busy(card);
+
+ return mmc_check_result(&mrq);
+}
+EXPORT_SYMBOL(mmc_simple_transfer);
+
+
+
#ifdef CONFIG_PM
/* Do the card removal on suspend if card is assumed removeable
diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
index 13ff8330..630fc6c1 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -697,6 +697,12 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd)
card->ext_csd.enhanced_rpmb_supported =
(card->ext_csd.rel_param &
EXT_CSD_WR_REL_PARAM_EN_RPMB_REL_WR);
+
+ card->ext_csd.ffu_capable =
+ ((ext_csd[EXT_CSD_SUPPORTED_MODE] & 0x1) == 0x1) &&
+ ((ext_csd[EXT_CSD_FW_CONFIG] & 0x1) == 0x0);
+ card->ext_csd.ffu_mode_op = ext_csd[EXT_CSD_FFU_FEATURES];
+
} else {
card->ext_csd.cmdq_support = 0;
card->ext_csd.cmdq_depth = 0;
@@ -812,6 +818,7 @@ MMC_DEV_ATTR(raw_rpmb_size_mult, "%#x\n", card->ext_csd.raw_rpmb_size_mult);
MMC_DEV_ATTR(enhanced_rpmb_supported, "%#x\n",
card->ext_csd.enhanced_rpmb_supported);
MMC_DEV_ATTR(rel_sectors, "%#x\n", card->ext_csd.rel_sectors);
+MMC_DEV_ATTR(firmware_version, "0x%08x\n", card->ext_csd.fw_version);
static struct attribute *mmc_std_attrs[] = {
&dev_attr_cid.attr,
@@ -831,6 +838,7 @@ static struct attribute *mmc_std_attrs[] = {
&dev_attr_raw_rpmb_size_mult.attr,
&dev_attr_enhanced_rpmb_supported.attr,
&dev_attr_rel_sectors.attr,
+ &dev_attr_firmware_version.attr,
NULL,
};
ATTRIBUTE_GROUPS(mmc_std);
@@ -1616,10 +1624,11 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
struct mmc_card *oldcard)
{
struct mmc_card *card;
- int err;
+ int err = 0;
u32 cid[4];
u32 rocr;
u8 *ext_csd = NULL;
+ bool scan = (oldcard == NULL);
BUG_ON(!host);
WARN_ON(!host->claimed);
@@ -1672,6 +1681,15 @@ reinit:
}
if (oldcard) {
+ if (oldcard->raw_cid[0] == 0 && oldcard->raw_cid[1] == 0 &&
+ oldcard->raw_cid[2] == 0 && oldcard->raw_cid[3] == 0) {
+ scan = true;
+ pr_info("%s: updating card identification\n", mmc_hostname(host));
+ memcpy(oldcard->raw_cid, cid, sizeof(oldcard->raw_cid));
+ err = mmc_decode_cid(oldcard);
+ if (err)
+ goto err;
+ }
if (memcmp(cid, oldcard->raw_cid, sizeof(cid)) != 0) {
err = -ENOENT;
pr_err("%s: %s: CID memcmp failed %d\n",
@@ -1714,7 +1732,7 @@ reinit:
mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
}
- if (!oldcard) {
+ if (scan) {
/*
* Fetch CSD from card.
*/
@@ -1758,7 +1776,7 @@ reinit:
}
}
- if (!oldcard) {
+ if (scan) {
/*
* Fetch and process extended CSD.
*/
@@ -2378,11 +2396,6 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend)
goto out;
}
- if (mmc_card_doing_auto_bkops(host->card)) {
- err = mmc_set_auto_bkops(host->card, false);
- if (err)
- goto out;
- }
err = mmc_flush_cache(host->card);
if (err)
@@ -2463,9 +2476,6 @@ static int mmc_partial_init(struct mmc_host *host)
pr_debug("%s: %s: reading and comparing ext_csd successful\n",
mmc_hostname(host), __func__);
- if (mmc_card_support_auto_bkops(host->card))
- (void)mmc_set_auto_bkops(host->card, true);
-
if (card->ext_csd.cmdq_support && (card->host->caps2 &
MMC_CAP2_CMD_QUEUE)) {
err = mmc_select_cmdq(card);
diff --git a/drivers/mmc/core/mmc_ffu.c b/drivers/mmc/core/mmc_ffu.c
new file mode 100644
index 00000000..0e45fa5c
--- /dev/null
+++ b/drivers/mmc/core/mmc_ffu.c
@@ -0,0 +1,522 @@
+/*
+ * * ffu.c
+ *
+ * Copyright 2007-2008 Pierre Ossman
+ *
+ * Modified by SanDisk Corp., Copyright © 2013 SanDisk Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program includes bug.h, card.h, host.h, mmc.h, scatterlist.h,
+ * slab.h, ffu.h & swap.h header files
+ * The original, unmodified version of this program – the mmc_test.c
+ * file – is obtained under the GPL v2.0 license that is available via
+ * http://www.gnu.org/licenses/,
+ * or http://www.opensource.org/licenses/gpl-2.0.php
+*/
+
+#include <linux/bug.h>
+#include <linux/errno.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+#include <linux/swap.h>
+#include <linux/firmware.h>
+#include <linux/reboot.h>
+
+/**
+ * struct mmc_ffu_pages - pages allocated by 'alloc_pages()'.
+ * @page: first page in the allocation
+ * @order: order of the number of pages allocated
+ */
+struct mmc_ffu_pages {
+ struct page *page;
+ unsigned int order;
+};
+
+/**
+ * struct mmc_ffu_mem - allocated memory.
+ * @arr: array of allocations
+ * @cnt: number of allocations
+ */
+struct mmc_ffu_mem {
+ struct mmc_ffu_pages *arr;
+ unsigned int cnt;
+};
+
+struct mmc_ffu_area {
+ unsigned long max_sz;
+ unsigned int max_tfr;
+ unsigned int max_segs;
+ unsigned int max_seg_sz;
+ unsigned int blocks;
+ unsigned int sg_len;
+ struct mmc_ffu_mem mem;
+ struct sg_table sgtable;
+};
+
+/*
+ * Map memory into a scatterlist.
+ */
+static unsigned int mmc_ffu_map_sg(struct mmc_ffu_mem *mem, int size,
+ struct scatterlist *sglist)
+{
+ struct scatterlist *sg = sglist;
+ unsigned int i;
+ unsigned long sz = size;
+ unsigned int sctr_len = 0;
+ unsigned long len;
+
+ for (i = 0; i < mem->cnt && sz; i++, sz -= len) {
+ len = PAGE_SIZE << mem->arr[i].order;
+
+ if (len > sz) {
+ len = sz;
+ sz = 0;
+ }
+
+ sg_set_page(sg, mem->arr[i].page, len, 0);
+ sg = sg_next(sg);
+ sctr_len++;
+ }
+
+ return sctr_len;
+}
+
+static void mmc_ffu_free_mem(struct mmc_ffu_mem *mem)
+{
+ if (!mem)
+ return;
+
+ while (mem->cnt--)
+ __free_pages(mem->arr[mem->cnt].page, mem->arr[mem->cnt].order);
+
+ kfree(mem->arr);
+}
+
+/*
+ * Cleanup struct mmc_ffu_area.
+ */
+static int mmc_ffu_area_cleanup(struct mmc_ffu_area *area)
+{
+ sg_free_table(&area->sgtable);
+ mmc_ffu_free_mem(&area->mem);
+ return 0;
+}
+
+/*
+ * Allocate a lot of memory, preferably max_sz but at least min_sz. In case
+ * there isn't much memory do not exceed 1/16th total low mem pages. Also do
+ * not exceed a maximum number of segments and try not to make segments much
+ * bigger than maximum segment size.
+ */
+static int mmc_ffu_alloc_mem(struct mmc_ffu_area *area, unsigned long min_sz)
+{
+ unsigned long max_page_cnt = DIV_ROUND_UP(area->max_tfr, PAGE_SIZE);
+ unsigned long min_page_cnt = DIV_ROUND_UP(min_sz, PAGE_SIZE);
+ unsigned long max_seg_page_cnt =
+ DIV_ROUND_UP(area->max_seg_sz, PAGE_SIZE);
+ unsigned long page_cnt = 0;
+ /* we divide by 16 to ensure we will not allocate a big amount
+ * of unnecessary pages */
+ unsigned long limit = nr_free_buffer_pages() >> 4;
+
+ gfp_t flags = GFP_KERNEL | GFP_DMA | __GFP_NOWARN | __GFP_NORETRY;
+
+ if (max_page_cnt > limit) {
+ max_page_cnt = limit;
+ area->max_tfr = max_page_cnt * PAGE_SIZE;
+ }
+
+ if (min_page_cnt > max_page_cnt)
+ min_page_cnt = max_page_cnt;
+
+ if (area->max_segs * max_seg_page_cnt > max_page_cnt)
+ area->max_segs = DIV_ROUND_UP(max_page_cnt, max_seg_page_cnt);
+
+ area->mem.arr = kzalloc(sizeof(struct mmc_ffu_pages) * area->max_segs,
+ GFP_KERNEL);
+ area->mem.cnt = 0;
+ if (!area->mem.arr)
+ goto out_free;
+
+ while (max_page_cnt) {
+ struct page *page;
+ unsigned int order;
+
+ order = get_order(max_seg_page_cnt << PAGE_SHIFT);
+
+ do {
+ page = alloc_pages(flags, order);
+ } while (!page && order--);
+
+ if (!page)
+ goto out_free;
+
+ area->mem.arr[area->mem.cnt].page = page;
+ area->mem.arr[area->mem.cnt].order = order;
+ area->mem.cnt++;
+ page_cnt += 1UL << order;
+ if (max_page_cnt <= (1UL << order))
+ break;
+ max_page_cnt -= 1UL << order;
+ }
+
+ if (page_cnt < min_page_cnt)
+ goto out_free;
+
+ return 0;
+
+out_free:
+ mmc_ffu_free_mem(&area->mem);
+ return -ENOMEM;
+}
+
+/*
+ * Initialize an area for data transfers.
+ * Copy the data to the allocated pages.
+ */
+static int mmc_ffu_area_init(struct mmc_ffu_area *area, struct mmc_card *card,
+ const u8 *data)
+{
+ int ret;
+ int i;
+ unsigned int length = 0, page_length;
+
+ ret = mmc_ffu_alloc_mem(area, 1);
+ for (i = 0; i < area->mem.cnt; i++) {
+ if (length > area->max_tfr) {
+ ret = -EINVAL;
+ goto out_free;
+ }
+ page_length = PAGE_SIZE << area->mem.arr[i].order;
+ memcpy(page_address(area->mem.arr[i].page), data + length,
+ min(area->max_tfr - length, page_length));
+ length += page_length;
+ }
+
+ ret = sg_alloc_table(&area->sgtable, area->mem.cnt, GFP_KERNEL);
+ if (ret)
+ goto out_free;
+
+ area->sg_len = mmc_ffu_map_sg(&area->mem, area->max_tfr,
+ area->sgtable.sgl);
+
+
+ return 0;
+
+out_free:
+ mmc_ffu_free_mem(&area->mem);
+ return ret;
+}
+
+static int mmc_ffu_write(struct mmc_card *card, const u8 *src, u32 arg,
+ int size)
+{
+ int rc;
+ struct mmc_ffu_area area = {0};
+ int block_size = card->ext_csd.data_sector_size;
+
+ area.max_segs = card->host->max_segs;
+ area.max_seg_sz = card->host->max_seg_size & ~(block_size - 1);
+
+ do {
+ area.max_tfr = size;
+ if (area.max_tfr >> 9 > card->host->max_blk_count)
+ area.max_tfr = card->host->max_blk_count << 9;
+ if (area.max_tfr > card->host->max_req_size)
+ area.max_tfr = card->host->max_req_size;
+ if (DIV_ROUND_UP(area.max_tfr, area.max_seg_sz) > area.max_segs)
+ area.max_tfr = area.max_segs * area.max_seg_sz;
+
+ rc = mmc_ffu_area_init(&area, card, src);
+ if (rc != 0)
+ goto exit;
+
+ rc = mmc_simple_transfer(card, area.sgtable.sgl, area.sg_len,
+ arg, area.max_tfr / block_size, block_size, 1);
+ mmc_ffu_area_cleanup(&area);
+ if (rc != 0) {
+ pr_err("%s mmc_ffu_simple_transfer %d\n", __func__, rc);
+ goto exit;
+ }
+ src += area.max_tfr;
+ size -= area.max_tfr;
+
+ } while (size > 0);
+
+exit:
+ return rc;
+}
+
+/* Flush all scheduled work from the MMC work queue.
+ * and initialize the MMC device */
+static int mmc_ffu_restart(struct mmc_card *card)
+{
+ struct mmc_host *host = card->host;
+ int err = 0;
+
+ /*
+ * Power cycle and hope for the best. On platforms that don't have a
+ * controllable regulator and/or hardware reset line, all we can do is
+ * hope that CMD0 is enough to apply the firmware.
+ */
+ err = mmc_power_save_host(host);
+ if (err) {
+ pr_warn("%s: FFU: %s: failed to power down (%d)\n",
+ mmc_hostname(card->host), __func__ , err);
+ goto exit;
+ }
+
+ /*
+ * Some eMMCs apply the new firmware right away, which looks like a card
+ * change if any of the CID fields change. There doesn't seem to be a
+ * good way to handle this for a non-removable card, so this hack
+ * signals the expected CID change to mmc_init_card().
+ */
+ memset(card->raw_cid, 0, sizeof(card->raw_cid));
+
+ err = mmc_power_restore_host(host);
+ if (err)
+ pr_warn("%s: FFU: %s: failed to power up (%d)\n",
+ mmc_hostname(card->host), __func__ , err);
+exit:
+ return err;
+}
+
+static int mmc_ffu_switch_mode(struct mmc_card *card , int mode)
+{
+ int err = 0;
+ int offset;
+
+ switch (mode) {
+ case MMC_FFU_MODE_SET:
+ case MMC_FFU_MODE_NORMAL:
+ offset = EXT_CSD_MODE_CONFIG;
+ break;
+ case MMC_FFU_INSTALL_SET:
+ offset = EXT_CSD_MODE_OPERATION_CODES;
+ mode = 0x1;
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+
+ if (err == 0) {
+ err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+ offset, mode,
+ card->ext_csd.generic_cmd6_time);
+ }
+
+ return err;
+}
+
+static int mmc_ffu_install(struct mmc_card *card, u8 *ext_csd)
+{
+ int err;
+ u32 timeout;
+
+ /* check mode operation */
+ timeout = ext_csd[EXT_CSD_OPERATION_CODE_TIMEOUT];
+ if (timeout == 0 || timeout > 0x17) {
+ timeout = 0x17;
+ pr_warn("%s: FFU: operation code timeout is out "
+ "of range. Using maximum timeout.\n",
+ mmc_hostname(card->host));
+ }
+
+ /* timeout is at millisecond resolution */
+ timeout = DIV_ROUND_UP((100 * (1 << timeout)), 1000);
+
+ /* set ext_csd to install mode */
+ err = mmc_ffu_switch_mode(card, MMC_FFU_INSTALL_SET);
+ if (err) {
+ pr_err("%s: FFU: error %d setting install mode\n",
+ mmc_hostname(card->host), err);
+ return err;
+ }
+
+ /* read ext_csd */
+ err = mmc_send_ext_csd(card, ext_csd);
+ if (err) {
+ pr_err("%s: FFU: error %d sending ext_csd\n",
+ mmc_hostname(card->host), err);
+ return err;
+ }
+
+ /* return status */
+ err = ext_csd[EXT_CSD_FFU_STATUS];
+ if (err) {
+ pr_err("%s: FFU: error %d FFU install:\n",
+ mmc_hostname(card->host), err);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int mmc_ffu_invoke(struct mmc_card *card, const char *name)
+{
+ u8 ext_csd[512];
+ int err;
+ u32 arg;
+ u32 fw_prog_bytes;
+ const struct firmware *fw;
+ int block_size = card->ext_csd.data_sector_size;
+
+ /* Check if FFU is supported */
+ if (!card->ext_csd.ffu_capable) {
+ pr_err("%s: FFU: error FFU is not supported %d rev %d\n",
+ mmc_hostname(card->host), card->ext_csd.ffu_capable,
+ card->ext_csd.rev);
+ return -EOPNOTSUPP;
+ }
+
+ if (strlen(name) > 512) {
+ pr_err("%s: FFU: %.20s is not a valid argument\n",
+ mmc_hostname(card->host), name);
+ return -EINVAL;
+ }
+
+ /* setup FW data buffer */
+ pr_info("%s: starting FFU using: %s\n", mmc_hostname(card->host), name);
+ err = request_firmware(&fw, name, &card->dev);
+ if (err) {
+ pr_err("%s: FFU: firmware request for %s failed %d\n",
+ mmc_hostname(card->host), name, err);
+ return err;
+ }
+ if ((fw->size % block_size)) {
+ pr_warn("%s: FFU: warning %zd firmware data size "
+ "is not aligned\n", mmc_hostname(card->host), fw->size);
+ }
+
+ mmc_claim_host(card->host);
+
+ if (mmc_card_cmdq(card)) {
+ /* halt cmdq engine */
+ err = mmc_cmdq_halt_on_empty_queue(card->host);
+ if (err) {
+ pr_err("fail to halt cmdq on host side err=%d\n", err);
+ goto exit;
+ }
+ /* disable cmdq mode */
+ err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_CMDQ, 0,
+ card->ext_csd.generic_cmd6_time);
+ if (err) {
+ pr_err("fail to disable cmdq mode in card=%d\n", err);
+ goto exit;
+ }
+ }
+
+
+ /* trigger flushing*/
+ err = mmc_flush_cache(card);
+ if (err) {
+ pr_err("%s: FFU: error %d flushing cache\n",
+ mmc_hostname(card->host), err);
+ goto exit;
+ }
+
+ /* Read the EXT_CSD */
+ err = mmc_send_ext_csd(card, ext_csd);
+ if (err) {
+ pr_err("%s: FFU: error %d reading EXT_CSD\n",
+ mmc_hostname(card->host), err);
+ goto exit;
+ }
+
+ /* set CMD ARG */
+ arg = ext_csd[EXT_CSD_FFU_ARG] |
+ ext_csd[EXT_CSD_FFU_ARG + 1] << 8 |
+ ext_csd[EXT_CSD_FFU_ARG + 2] << 16 |
+ ext_csd[EXT_CSD_FFU_ARG + 3] << 24;
+
+ /* set device to FFU mode */
+ err = mmc_ffu_switch_mode(card, MMC_FFU_MODE_SET);
+ if (err) {
+ pr_err("%s: FFU: error %d FFU is not supported\n",
+ mmc_hostname(card->host), err);
+ goto exit;
+ }
+
+ err = mmc_ffu_write(card, fw->data, arg, fw->size);
+ if (err) {
+ pr_err("%s: FFU: write error %d\n",
+ mmc_hostname(card->host), err);
+ goto exit;
+ }
+ /* payload will be checked only in op_mode supported */
+ if (card->ext_csd.ffu_mode_op) {
+ /* Read the EXT_CSD */
+ err = mmc_send_ext_csd(card, ext_csd);
+ if (err) {
+ pr_err("%s: FFU: error %d sending ext_csd\n",
+ mmc_hostname(card->host), err);
+ goto exit;
+ }
+
+ /* check that the eMMC has received the payload */
+ fw_prog_bytes = ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG] |
+ ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 1] << 8 |
+ ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 2] << 16 |
+ ext_csd[EXT_CSD_NUM_OF_FW_SEC_PROG + 3] << 24;
+
+ /* convert sectors to bytes: multiply by -512B or 4KB as
+ required by the card */
+ fw_prog_bytes *=
+ block_size << (ext_csd[EXT_CSD_DATA_SECTOR_SIZE] * 3);
+ if (fw_prog_bytes != fw->size) {
+ err = -EINVAL;
+ pr_err("%s: FFU: error %d number of programmed fw sector "
+ "incorrect %d %zd\n", __func__, err,
+ fw_prog_bytes, fw->size);
+ goto exit;
+ }
+
+ err = mmc_ffu_install(card, ext_csd);
+ if (err) {
+ pr_err("%s: FFU: error firmware install %d\n",
+ mmc_hostname(card->host), err);
+ mmc_ffu_switch_mode(card, MMC_FFU_MODE_NORMAL);
+ goto exit;
+ }
+ } else {
+ /* host switch back to work in normal MMC Read/Write commands */
+ err = mmc_ffu_switch_mode(card, MMC_FFU_MODE_NORMAL);
+ if (err)
+ pr_err("%s: FFU: switch to normal mode error %d (ignoring)\n",
+ mmc_hostname(card->host), err);
+ }
+
+ if (!card->ext_csd.ffu_mode_op) {
+ /* restart the eMMC */
+ err = mmc_ffu_restart(card);
+ if (err) {
+ pr_err("%s: FFU: failed to restart device %d\n",
+ mmc_hostname(card->host), err);
+ goto exit;
+ }
+ }
+
+ if (mmc_card_cmdq(card)) {
+ /* Do a power cycle after FW is updated
+ //TODO: find a more grace way to avoid power cycle.
+ */
+ pr_info("eMMC firmware updated, reboot now\n");
+ kernel_restart(NULL);
+ }
+
+exit:
+ mmc_release_host(card->host);
+ release_firmware(fw);
+ return err;
+}
+EXPORT_SYMBOL(mmc_ffu_invoke);
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index c7a06681..c6582b0f 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -3012,7 +3012,8 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask)
host->ops->adma_workaround(host, intmask);
}
if (host->data->error) {
- if (intmask & (SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_TIMEOUT)) {
+ if (intmask & (SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_TIMEOUT
+ | SDHCI_INT_DATA_END_BIT)) {
command = SDHCI_GET_CMD(sdhci_readw(host,
SDHCI_COMMAND));
if ((command != MMC_SEND_TUNING_BLOCK_HS200) &&
@@ -4116,10 +4117,15 @@ int sdhci_add_host(struct sdhci_host *host)
* value.
*/
max_current_caps = sdhci_readl(host, SDHCI_MAX_CURRENT);
- if (!max_current_caps && !IS_ERR(mmc->supply.vmmc)) {
- int curr = regulator_get_current_limit(mmc->supply.vmmc);
- if (curr > 0) {
+ if (!max_current_caps) {
+ u32 curr = 0;
+
+ if (!IS_ERR(mmc->supply.vmmc))
+ curr = regulator_get_current_limit(mmc->supply.vmmc);
+ else if (host->ops->get_current_limit)
+ curr = host->ops->get_current_limit(host);
+ if (curr > 0) {
/* convert to SDHCI_MAX_CURRENT format */
curr = curr/1000; /* convert to mA */
curr = curr/SDHCI_MAX_CURRENT_MULTIPLIER;
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index 80f36d61..aaba8884 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -337,6 +337,7 @@ struct sdhci_ops {
void (*init)(struct sdhci_host *host);
void (*pre_req)(struct sdhci_host *host, struct mmc_request *req);
void (*post_req)(struct sdhci_host *host, struct mmc_request *req);
+ unsigned int (*get_current_limit)(struct sdhci_host *host);
};
#ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS