| /* |
| * This file contains work-arounds for many known SD/MMC |
| * and SDIO hardware bugs. |
| * |
| * Copyright (c) 2011 Andrei Warkentin <andreiw@motorola.com> |
| * Copyright (c) 2011 Pierre Tardy <tardyp@gmail.com> |
| * Inspired from pci fixup code: |
| * Copyright (c) 1999 Martin Mares <mj@ucw.cz> |
| * |
| */ |
| |
| #include <linux/slab.h> |
| #include <linux/types.h> |
| #include <linux/scatterlist.h> |
| #include <linux/kernel.h> |
| #include <linux/mmc/card.h> |
| #include <linux/mmc/mmc.h> |
| #include <linux/mmc/host.h> |
| |
| #include "mmc_ops.h" |
| |
| #ifndef SDIO_VENDOR_ID_TI |
| #define SDIO_VENDOR_ID_TI 0x0097 |
| #endif |
| |
| #ifndef SDIO_DEVICE_ID_TI_WL1271 |
| #define SDIO_DEVICE_ID_TI_WL1271 0x4076 |
| #endif |
| |
| /* |
| * This hook just adds a quirk for all sdio devices |
| */ |
| static void add_quirk_for_sdio_devices(struct mmc_card *card, int data) |
| { |
| if (mmc_card_sdio(card)) |
| card->quirks |= data; |
| } |
| |
| static const struct mmc_fixup mmc_fixup_methods[] = { |
| /* by default sdio devices are considered CLK_GATING broken */ |
| /* good cards will be whitelisted as they are tested */ |
| SDIO_FIXUP(SDIO_ANY_ID, SDIO_ANY_ID, |
| add_quirk_for_sdio_devices, |
| MMC_QUIRK_BROKEN_CLK_GATING), |
| |
| SDIO_FIXUP(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271, |
| remove_quirk, MMC_QUIRK_BROKEN_CLK_GATING), |
| |
| SDIO_FIXUP(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271, |
| add_quirk, MMC_QUIRK_NONSTD_FUNC_IF), |
| |
| SDIO_FIXUP(SDIO_VENDOR_ID_TI, SDIO_DEVICE_ID_TI_WL1271, |
| add_quirk, MMC_QUIRK_DISABLE_CD), |
| |
| END_FIXUP |
| }; |
| |
| void mmc_fixup_device(struct mmc_card *card, const struct mmc_fixup *table) |
| { |
| const struct mmc_fixup *f; |
| u64 rev = cid_rev_card(card); |
| |
| /* Non-core specific workarounds. */ |
| if (!table) |
| table = mmc_fixup_methods; |
| |
| for (f = table; f->vendor_fixup; f++) { |
| if ((f->manfid == CID_MANFID_ANY || |
| f->manfid == card->cid.manfid) && |
| (f->oemid == CID_OEMID_ANY || |
| f->oemid == card->cid.oemid) && |
| (f->name == CID_NAME_ANY || |
| !strncmp(f->name, card->cid.prod_name, |
| sizeof(card->cid.prod_name))) && |
| (f->cis_vendor == card->cis.vendor || |
| f->cis_vendor == (u16) SDIO_ANY_ID) && |
| (f->cis_device == card->cis.device || |
| f->cis_device == (u16) SDIO_ANY_ID) && |
| rev >= f->rev_start && rev <= f->rev_end) { |
| dev_dbg(&card->dev, "calling %pF\n", f->vendor_fixup); |
| f->vendor_fixup(card, f->data); |
| } |
| } |
| } |
| EXPORT_SYMBOL(mmc_fixup_device); |
| |
| /* |
| * Quirk code to fix bug in wear leveling firmware for certain Samsung emmc |
| * chips |
| */ |
| static int mmc_movi_vendor_cmd(struct mmc_card *card, unsigned int arg) |
| { |
| struct mmc_command cmd = {0}; |
| int err; |
| u32 status; |
| |
| /* CMD62 is vendor CMD, it's not defined in eMMC spec. */ |
| cmd.opcode = 62; |
| cmd.flags = MMC_RSP_R1B; |
| cmd.arg = arg; |
| |
| err = mmc_wait_for_cmd(card->host, &cmd, 0); |
| if (err) |
| return err; |
| |
| do { |
| err = mmc_send_status(card, &status); |
| if (err) |
| return err; |
| if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY) |
| break; |
| if (mmc_host_is_spi(card->host)) |
| break; |
| } while (R1_CURRENT_STATE(status) == R1_STATE_PRG); |
| |
| return err; |
| } |
| |
| static int mmc_movi_erase_cmd(struct mmc_card *card, |
| unsigned int arg1, unsigned int arg2) |
| { |
| struct mmc_command cmd = {0}; |
| int err; |
| u32 status; |
| |
| cmd.opcode = MMC_ERASE_GROUP_START; |
| cmd.flags = MMC_RSP_R1; |
| cmd.arg = arg1; |
| |
| err = mmc_wait_for_cmd(card->host, &cmd, 0); |
| if (err) |
| return err; |
| |
| memset(&cmd, 0, sizeof(struct mmc_command)); |
| cmd.opcode = MMC_ERASE_GROUP_END; |
| cmd.flags = MMC_RSP_R1; |
| cmd.arg = arg2; |
| |
| err = mmc_wait_for_cmd(card->host, &cmd, 0); |
| if (err) |
| return err; |
| |
| memset(&cmd, 0, sizeof(struct mmc_command)); |
| cmd.opcode = MMC_ERASE; |
| cmd.flags = MMC_RSP_R1B; |
| cmd.arg = 0x00000000; |
| |
| err = mmc_wait_for_cmd(card->host, &cmd, 0); |
| if (err) |
| return err; |
| |
| do { |
| err = mmc_send_status(card, &status); |
| if (err) |
| return err; |
| if (card->host->caps & MMC_CAP_WAIT_WHILE_BUSY) |
| break; |
| if (mmc_host_is_spi(card->host)) |
| break; |
| } while (R1_CURRENT_STATE(status) == R1_STATE_PRG); |
| |
| return err; |
| } |
| |
| #define TEST_MMC_FW_PATCHING |
| |
| #if defined(CONFIG_MMC_SAMSUNG_SMART) || defined(TEST_MMC_FW_PATCHING) |
| static struct mmc_command wcmd; |
| static struct mmc_data wdata; |
| |
| static int mmc_movi_read_cmd(struct mmc_card *card, u8 *buffer) |
| { |
| struct mmc_request brq; |
| struct scatterlist sg; |
| |
| brq.cmd = &wcmd; |
| brq.data = &wdata; |
| |
| wcmd.opcode = MMC_READ_SINGLE_BLOCK; |
| wcmd.arg = 0; |
| wcmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; |
| wdata.blksz = 512; |
| brq.stop = NULL; |
| wdata.blocks = 1; |
| wdata.flags = MMC_DATA_READ; |
| |
| wdata.sg = &sg; |
| wdata.sg_len = 1; |
| |
| sg_init_one(&sg, buffer, 512); |
| |
| mmc_set_data_timeout(&wdata, card); |
| |
| mmc_wait_for_req(card->host, &brq); |
| if (wcmd.error) |
| return wcmd.error; |
| if (wdata.error) |
| return wdata.error; |
| return 0; |
| } |
| #endif /* CONFIG_MMC_SAMSUNG_SMART || TEST_MMC_FW_PATCHING */ |
| |
| /* |
| * Copy entire page when wear leveling is happened |
| */ |
| static int mmc_set_wearlevel_page(struct mmc_card *card) |
| { |
| int err, errx = 0; |
| #ifdef TEST_MMC_FW_PATCHING |
| void *buffer; |
| buffer = kmalloc(512, GFP_KERNEL); |
| if (!buffer) { |
| pr_err("Fail to alloc memory for set wearlevel\n"); |
| return -ENOMEM; |
| } |
| #endif /* TEST_MMC_FW_PATCHING */ |
| |
| /* modification vendor cmd */ |
| /* enter vendor command mode */ |
| err = mmc_movi_vendor_cmd(card, 0xEFAC62EC); |
| if (err) |
| goto out; |
| |
| err = mmc_movi_vendor_cmd(card, 0x10210000); |
| if (err) |
| goto out; |
| |
| /* set value 0x000000FF : It's hidden data |
| * When in vendor command mode, the erase command is used to |
| * patch the firmware in the internal sram. |
| */ |
| err = mmc_movi_erase_cmd(card, 0x0004DD9C, 0x000000FF); |
| if (err) { |
| pr_err("Fail to Set WL value1\n"); |
| goto err_set_wl; |
| } |
| |
| /* set value 0xD20228FF : It's hidden data */ |
| err = mmc_movi_erase_cmd(card, 0x000379A4, 0xD20228FF); |
| if (err) { |
| pr_err("Fail to Set WL value2\n"); |
| goto err_set_wl; |
| } |
| |
| err_set_wl: |
| /* exit vendor command mode */ |
| errx = mmc_movi_vendor_cmd(card, 0xEFAC62EC); |
| if (errx) |
| goto out; |
| |
| errx = mmc_movi_vendor_cmd(card, 0x00DECCEE); |
| if (errx || err) |
| goto out; |
| |
| #ifdef TEST_MMC_FW_PATCHING |
| /* read and verify vendor cmd */ |
| /* enter vendor cmd */ |
| err = mmc_movi_vendor_cmd(card, 0xEFAC62EC); |
| if (err) |
| goto out; |
| |
| err = mmc_movi_vendor_cmd(card, 0x10210002); |
| if (err) |
| goto out; |
| |
| err = mmc_movi_erase_cmd(card, 0x0004DD9C, 0x00000004); |
| if (err) { |
| pr_err("Fail to Check WL value1\n"); |
| goto err_check_wl; |
| } |
| |
| err = mmc_movi_read_cmd(card, (u8 *)buffer); |
| if (err) { |
| pr_err("Fail to Read value1\n"); |
| goto err_check_wl; |
| } |
| pr_debug("buffer[0] is 0x%x\n", *(u8 *)buffer); |
| |
| err = mmc_movi_erase_cmd(card, 0x000379A4, 0x00000004); |
| if (err) { |
| pr_err("Fail to Check WL value2\n"); |
| goto err_check_wl; |
| } |
| |
| err = mmc_movi_read_cmd(card, (u8 *)buffer); |
| if (err) { |
| pr_err("Fail to Read value2\n"); |
| goto err_check_wl; |
| } |
| pr_debug("buffer[0] is 0x%x\n", *(u8 *)buffer); |
| |
| err_check_wl: |
| /* exit vendor cmd mode */ |
| errx = mmc_movi_vendor_cmd(card, 0xEFAC62EC); |
| if (errx) |
| goto out; |
| |
| errx = mmc_movi_vendor_cmd(card, 0x00DECCEE); |
| if (errx || err) |
| goto out; |
| |
| #endif /* TEST_MMC_FW_PATCHING */ |
| |
| out: |
| #ifdef TEST_MMC_FW_PATCHING |
| kfree(buffer); |
| #endif /* TEST_MMC_FW_PATCHING */ |
| if (err) |
| return err; |
| return errx; |
| } |
| |
| void mmc_fixup_samsung_fw(struct mmc_card *card) |
| { |
| int err; |
| |
| mmc_claim_host(card->host); |
| err = mmc_set_wearlevel_page(card); |
| mmc_release_host(card->host); |
| if (err) |
| pr_err("%s : Failed to fixup Samsung emmc firmware(%d)\n", |
| mmc_hostname(card->host), err); |
| } |
| |
| #ifdef CONFIG_MMC_SAMSUNG_SMART |
| static int mmc_samsung_smart_read(struct mmc_card *card, u8 *rdblock) |
| { |
| int err, errx; |
| |
| /* enter vendor Smart Report mode */ |
| err = mmc_movi_vendor_cmd(card, 0xEFAC62EC); |
| if (err) { |
| pr_err("Failed entering Smart Report mode(1, %d)\n", err); |
| return err; |
| } |
| err = mmc_movi_vendor_cmd(card, 0x0000CCEE); |
| if (err) { |
| pr_err("Failed entering Smart Report mode(2, %d)\n", err); |
| return err; |
| } |
| |
| /* read Smart Report */ |
| err = mmc_movi_read_cmd(card, rdblock); |
| if (err) |
| pr_err("Failed reading Smart Report(%d)\n", err); |
| /* Do NOT return yet; we must leave Smart Report mode.*/ |
| |
| /* exit vendor Smart Report mode */ |
| errx = mmc_movi_vendor_cmd(card, 0xEFAC62EC); |
| if (errx) |
| pr_err("Failed exiting Smart Report mode(1, %d)\n", errx); |
| else { |
| errx = mmc_movi_vendor_cmd(card, 0x00DECCEE); |
| if (errx) |
| pr_err("Failed exiting Smart Report mode(2, %d)\n", |
| errx); |
| } |
| if (err) |
| return err; |
| return errx; |
| } |
| ssize_t mmc_samsung_smart_parse(u32 *report, char *for_sysfs) |
| { |
| unsigned size = PAGE_SIZE; |
| unsigned wrote; |
| unsigned i; |
| u32 val; |
| char *str; |
| static const struct { |
| char *fmt; |
| unsigned val_index; |
| } to_output[] = { |
| { "super block size : %u\n", 1 }, |
| { "super page size : %u\n", 2 }, |
| { "optimal write size : %u\n", 3 }, |
| { "read reclaim count : %u\n", 20 }, |
| { "optimal trim size : %u\n", 21 }, |
| { "number of banks : %u\n", 4 }, |
| { "initial bad blocks per bank : %u", 5 }, |
| { ",%u", 8 }, |
| { ",%u", 11 }, |
| { ",%u\n", 14 }, |
| { "runtime bad blocks per bank : %u", 6 }, |
| { ",%u", 9 }, |
| { ",%u", 12 }, |
| { ",%u\n", 15 }, |
| { "reserved blocks left per bank : %u", 7 }, |
| { ",%u", 10 }, |
| { ",%u", 13 }, |
| { ",%u\n", 16 }, |
| { "all erase counts (min,avg,max): %u", 18 }, |
| { ",%u", 19 }, |
| { ",%u\n", 17 }, |
| { "SLC erase counts (min,avg,max): %u", 31 }, |
| { ",%u", 32 }, |
| { ",%u\n", 30 }, |
| { "MLC erase counts (min,avg,max): %u", 34 }, |
| { ",%u", 35 }, |
| { ",%u\n", 33 }, |
| }; |
| |
| /* A version field just in case things change. */ |
| wrote = scnprintf(for_sysfs, size, |
| "version : %u\n", 0); |
| size -= wrote; |
| for_sysfs += wrote; |
| |
| /* The error mode. */ |
| val = le32_to_cpu(report[0]); |
| switch (val) { |
| case 0xD2D2D2D2: |
| str = "Normal"; |
| break; |
| case 0x5C5C5C5C: |
| str = "RuntimeFatalError"; |
| break; |
| case 0xE1E1E1E1: |
| str = "MetaBrokenError"; |
| break; |
| case 0x37373737: |
| str = "OpenFatalError"; |
| val = 0; /* Remaining data is unreliable. */ |
| break; |
| default: |
| str = "Invalid"; |
| val = 0; /* Remaining data is unreliable. */ |
| break; |
| } |
| wrote = scnprintf(for_sysfs, size, |
| "error mode : %s\n", str); |
| size -= wrote; |
| for_sysfs += wrote; |
| /* Exit if we can't rely on the remaining data. */ |
| if (!val) |
| return PAGE_SIZE - size; |
| |
| for (i = 0; i < ARRAY_SIZE(to_output); i++) { |
| wrote = scnprintf(for_sysfs, size, to_output[i].fmt, |
| le32_to_cpu(report[to_output[i].val_index])); |
| size -= wrote; |
| for_sysfs += wrote; |
| } |
| |
| return PAGE_SIZE - size; |
| } |
| ssize_t mmc_samsung_smart_handle(struct mmc_card *card, char *buf) |
| { |
| int err; |
| u32 *buffer; |
| ssize_t len; |
| |
| buffer = kmalloc(512, GFP_KERNEL); |
| if (!buffer) { |
| pr_err("Failed to alloc memory for Smart Report\n"); |
| return 0; |
| } |
| |
| mmc_claim_host(card->host); |
| err = mmc_samsung_smart_read(card, (u8 *)buffer); |
| mmc_release_host(card->host); |
| |
| if (err) |
| len = 0; |
| else |
| len = mmc_samsung_smart_parse(buffer, buf); |
| |
| kfree(buffer); |
| return len; |
| } |
| #endif /* CONFIG_MMC_SAMSUNG_SMART */ |