diff options
Diffstat (limited to 'drivers/mmc')
| -rw-r--r-- | drivers/mmc/card/Kconfig | 7 | ||||
| -rw-r--r-- | drivers/mmc/card/block.c | 5 | ||||
| -rw-r--r-- | drivers/mmc/core/Makefile | 1 | ||||
| -rw-r--r-- | drivers/mmc/core/core.c | 131 | ||||
| -rw-r--r-- | drivers/mmc/core/mmc.c | 32 | ||||
| -rw-r--r-- | drivers/mmc/core/mmc_ffu.c | 522 | ||||
| -rw-r--r-- | drivers/mmc/host/sdhci.c | 14 | ||||
| -rw-r--r-- | drivers/mmc/host/sdhci.h | 1 |
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 |
