Merge "msm_shared: mmc: Add support for erase and WP function"
diff --git a/platform/msm_shared/include/mmc_sdhci.h b/platform/msm_shared/include/mmc_sdhci.h
index 99174bf..0c6c3eb 100644
--- a/platform/msm_shared/include/mmc_sdhci.h
+++ b/platform/msm_shared/include/mmc_sdhci.h
@@ -80,6 +80,7 @@
#define MMC_SWITCH_FUNC_ERR_FLAG (1 << 7)
#define MMC_STATUS_INACTIVE 0
#define MMC_STATUS_ACTIVE 1
+#define MMC_READY_FOR_DATA (1 << 8)
/* EXT_CSD */
/* Offsets in the ext csd */
@@ -94,23 +95,32 @@
#define MMC_PART_CONFIG 179
#define MMC_ERASE_GRP_DEF 175
#define MMC_USR_WP 171
+#define MMC_HC_ERASE_GRP_SIZE 224
/* Values for ext csd fields */
#define MMC_HS_TIMING 0x1
#define MMC_HS200_TIMING 0x2
#define MMC_ACCESS_WRITE 0x3
+#define MMC_SET_BIT 0x1
#define MMC_HS_DDR_MODE (BIT(2) | BIT(3))
#define MMC_HS_HS200_MODE (BIT(4) | BIT(5))
#define MMC_SEC_COUNT4_SHIFT 24
#define MMC_SEC_COUNT3_SHIFT 16
#define MMC_SEC_COUNT2_SHIFT 8
+#define MMC_HC_ERASE_MULT (512 * 1024)
/* Command related */
#define MMC_MAX_COMMAND_RETRY 1000
+#define MMC_MAX_CARD_STAT_RETRY 10000
#define MMC_RD_BLOCK_LEN 512
#define MMC_WR_BLOCK_LEN 512
#define MMC_R1_BLOCK_LEN_ERR (1 << 29)
#define MMC_R1_ADDR_ERR (1 << 30)
+#define MMC_R1_WP_ERASE_SKIP BIT(15)
+#define MMC_US_PERM_WP_DIS BIT(4)
+#define MMC_US_PWR_WP_DIS BIT(3)
+#define MMC_US_PERM_WP_EN BIT(2)
+#define MMC_US_PWR_WP_EN BIT(0)
/* RCA of the card */
#define MMC_RCA 2
@@ -128,6 +138,8 @@
#define MMC_CLK_96MHZ 96000000
#define MMC_CLK_200MHZ 200000000
+#define MMC_ADDR_OUT_OF_RANGE(resp) ((resp >> 31) & 0x01)
+
/* Can be used to unpack array of upto 32 bits data */
#define UNPACK_BITS(array, start, len, size_of) \
({ \
@@ -218,4 +230,10 @@
uint32_t mmc_sdhci_read(struct mmc_device *dev, void *dest, uint64_t blk_addr, uint32_t num_blocks);
/* API: Write requried number of blocks from source to card */
uint32_t mmc_sdhci_write(struct mmc_device *dev, void *src, uint64_t blk_addr, uint32_t num_blocks);
+/* API: Erase len bytes (after converting to number of erase groups), from specified address */
+uint32_t mmc_sdhci_erase(struct mmc_device *dev, uint32_t blk_addr, uint64_t len);
+/* API: Write protect or release len bytes (after converting to number of write protect groups) from specified start address*/
+uint32_t mmc_set_clr_power_on_wp_user(struct mmc_device *dev, uint32_t addr, uint64_t len, uint8_t set_clr);
+/* API: Get the WP status of write protect groups starting at addr */
+uint32_t mmc_get_wp_status(struct mmc_device *dev, uint32_t addr, uint8_t *wp_status);
#endif
diff --git a/platform/msm_shared/mmc_sdhci.c b/platform/msm_shared/mmc_sdhci.c
index b565d9e..bfdeeef 100644
--- a/platform/msm_shared/mmc_sdhci.c
+++ b/platform/msm_shared/mmc_sdhci.c
@@ -1313,3 +1313,331 @@
return mmc_ret;
}
+
+/*
+ * Send the erase group start address using CMD35
+ */
+static uint32_t mmc_send_erase_grp_start(struct mmc_device *dev, uint32_t erase_start)
+{
+ struct mmc_command cmd;
+
+ memset((struct mmc_command *)&cmd, 0, sizeof(struct mmc_command));
+
+ cmd.cmd_index = CMD35_ERASE_GROUP_START;
+ cmd.argument = erase_start;
+ cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
+ cmd.resp_type = SDHCI_CMD_RESP_R1;
+
+ /* send command */
+ if (sdhci_send_command(&dev->host, &cmd))
+ return 1;
+
+ /*
+ * CMD35 on failure returns address out of range error
+ */
+ if (MMC_ADDR_OUT_OF_RANGE(cmd.resp[0]))
+ {
+ dprintf(CRITICAL, "Address for CMD35 is out of range\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Send the erase group end address using CMD36
+ */
+static uint32_t mmc_send_erase_grp_end(struct mmc_device *dev, uint32_t erase_end)
+{
+ struct mmc_command cmd;
+
+ memset((struct mmc_command *)&cmd, 0, sizeof(struct mmc_command));
+
+ cmd.cmd_index = CMD36_ERASE_GROUP_END;
+ cmd.argument = erase_end;
+ cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
+ cmd.resp_type = SDHCI_CMD_RESP_R1;
+
+ /* send command */
+ if (sdhci_send_command(&dev->host, &cmd))
+ return 1;
+
+ /*
+ * CMD3 on failure returns address out of range error
+ */
+ if (MMC_ADDR_OUT_OF_RANGE(cmd.resp[0]))
+ {
+ dprintf(CRITICAL, "Address for CMD36 is out of range\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Send the erase CMD38, to erase the selected erase groups
+ */
+static uint32_t mmc_send_erase(struct mmc_device *dev)
+{
+ struct mmc_command cmd;
+ uint32_t status;
+ uint32_t retry;
+
+ memset((struct mmc_command *)&cmd, 0, sizeof(struct mmc_command));
+
+ cmd.cmd_index = CMD38_ERASE;
+ cmd.argument = 0x00000000;
+ cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
+ cmd.resp_type = SDHCI_CMD_RESP_R1B;
+
+ /* send command */
+ if (sdhci_send_command(&dev->host, &cmd))
+ return 1;
+
+ do
+ {
+ if (mmc_get_card_status(&dev->host, &dev->card, &status))
+ {
+ dprintf(CRITICAL, "Failed to get card status after erase\n");
+ return 1;
+ }
+ /* Check if the response of erase command has eras skip status set */
+ if (status & MMC_R1_WP_ERASE_SKIP)
+ dprintf(CRITICAL, "Write Protect set for the region, only partial space was erased\n");
+
+ retry++;
+ udelay(1000);
+ if (retry == MMC_MAX_CARD_STAT_RETRY)
+ {
+ dprintf(CRITICAL, "Card status check timed out after sending erase command\n");
+ return 1;
+ }
+ } while(!(status & MMC_READY_FOR_DATA) || (MMC_CARD_STATUS(status) == MMC_PROG_STATE));
+
+
+ return 0;
+}
+
+
+/*
+ * Function: mmc sdhci erase
+ * Arg : mmc device structure, block address and length
+ * Return : 0 on Success, non zero on failure
+ * Flow : Fill in the command structure & send the command
+ */
+uint32_t mmc_sdhci_erase(struct mmc_device *dev, uint32_t blk_addr, uint64_t len)
+{
+ uint32_t erase_unit_sz = 0;
+ uint32_t erase_start;
+ uint32_t erase_end;
+ uint32_t blk_end;
+ uint32_t num_erase_grps;
+ uint32_t *out;
+
+ /*
+ * Calculate the erase unit size as per the emmc specification v4.5
+ */
+ if (dev->card.ext_csd[MMC_ERASE_GRP_DEF])
+ erase_unit_sz = (MMC_HC_ERASE_MULT * dev->card.ext_csd[MMC_HC_ERASE_GRP_SIZE]) / MMC_BLK_SZ;
+ else
+ erase_unit_sz = (dev->card.csd.erase_grp_size + 1) * (dev->card.csd.erase_grp_mult + 1);
+
+ /* Convert length in blocks */
+ len = len / MMC_BLK_SZ;
+
+ if (len < erase_unit_sz)
+ {
+ dprintf(CRITICAL, "Requested length is less than min erase group size\n");
+ return 1;
+ }
+
+ /* Calculate erase groups based on the length in blocks */
+ num_erase_grps = len / erase_unit_sz;
+
+ /* Start address of the erase range */
+ erase_start = blk_addr;
+
+ /* Last address of the erase range */
+ erase_end = blk_addr + ((num_erase_grps - 1) * erase_unit_sz);
+
+ /* Boundary check for overlap */
+ blk_end = blk_addr + len;
+
+ if (erase_end > blk_end)
+ {
+ dprintf(CRITICAL, "The erase group overlaps the max requested for erase\n");
+ erase_end -= erase_unit_sz;
+ }
+
+ /* Send CMD35 for erase group start */
+ if (mmc_send_erase_grp_start(dev, erase_start))
+ {
+ dprintf(CRITICAL, "Failed to send erase grp start address\n");
+ return 1;
+ }
+
+ /* Send CMD36 for erase group end */
+ if (mmc_send_erase_grp_end(dev, erase_end))
+ {
+ dprintf(CRITICAL, "Failed to send erase grp end address\n");
+ return 1;
+ }
+
+ /* Send CMD38 to perform erase */
+ if (mmc_send_erase(dev))
+ {
+ dprintf(CRITICAL, "Failed to erase the specified partition\n");
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Function: mmc get wp status
+ * Arg : mmc device structure, block address and buffer for getting wp status
+ * Return : 0 on Success, 1 on Failure
+ * Flow : Get the WP group status by sending CMD31
+ */
+uint32_t mmc_get_wp_status(struct mmc_device *dev, uint32_t addr, uint8_t *wp_status)
+{
+ struct mmc_command cmd;
+
+ memset((struct mmc_command *)&cmd, 0, sizeof(struct mmc_command));
+
+ cmd.cmd_index = CMD31_SEND_WRITE_PROT_TYPE;
+ cmd.argument = addr;
+ cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
+ cmd.resp_type = SDHCI_CMD_RESP_R1;
+ cmd.trans_mode = SDHCI_MMC_READ;
+ cmd.data_present = 0x1;
+ cmd.data.data_ptr = wp_status;
+ cmd.data.num_blocks = 0x1;
+ cmd.data.blk_sz = 0x8;
+
+ if (sdhci_send_command(&dev->host, &cmd))
+ {
+ dprintf(CRITICAL, "Failed to get status of write protect bits\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Function: mmc set/clear WP on user area
+ * Arg : mmc device structure, block address,len, & flag to set or clear
+ * Return : 0 on success, 1 on failure
+ * Flow : Function to set/clear power on write protect on user area
+ */
+
+uint32_t mmc_set_clr_power_on_wp_user(struct mmc_device *dev, uint32_t addr, uint64_t len, uint8_t set_clr)
+{
+ struct mmc_command cmd;
+ uint32_t wp_grp_size;
+ uint32_t status;
+ uint32_t num_wp_grps;
+ uint32_t ret;
+ uint32_t retry;
+ uint32_t i;
+
+ memset((struct mmc_command *)&cmd, 0, sizeof(struct mmc_command));
+
+ /* Convert len into blocks */
+ len = len / MMC_BLK_SZ;
+
+ /* Disable PERM WP */
+ ret = mmc_switch_cmd(&dev->host, &dev->card, MMC_SET_BIT, MMC_USR_WP, MMC_US_PERM_WP_DIS);
+
+ if (ret)
+ {
+ dprintf(CRITICAL, "Failed to Disable PERM WP\n");
+ return ret;
+ }
+
+ /* Read the default values for user WP */
+ ret = mmc_get_ext_csd(&dev->host, &dev->card);
+
+ if (ret)
+ {
+ dprintf(CRITICAL, "Failed to read ext csd for the card\n");
+ return ret;
+ }
+
+ /* Check if user power on WP is disabled or perm WP is enabled */
+ if ((dev->card.ext_csd[MMC_USR_WP] & MMC_US_PWR_WP_DIS)
+ || (dev->card.ext_csd[MMC_USR_WP] & MMC_US_PERM_WP_EN))
+ {
+ dprintf(CRITICAL, "Power on protection is disabled, cannot be set\n");
+ return 1;
+ }
+
+ /* Calculate the wp grp size */
+ if (dev->card.ext_csd[MMC_ERASE_GRP_DEF])
+ wp_grp_size = MMC_HC_ERASE_MULT * dev->card.ext_csd[MMC_HC_ERASE_GRP_SIZE] / MMC_BLK_SZ;
+ else
+ wp_grp_size = (dev->card.csd.wp_grp_size + 1) * (dev->card.csd.erase_grp_size + 1) \
+ * (dev->card.csd.erase_grp_mult + 1);
+
+
+ if (len < wp_grp_size)
+ {
+ dprintf(CRITICAL, "Length is less than min WP size, WP was not set\n");
+ return 1;
+ }
+
+ /* Set power on USER WP */
+ ret = mmc_switch_cmd(&dev->host, &dev->card, MMC_SET_BIT, MMC_USR_WP, MMC_US_PWR_WP_EN);
+
+ if (ret)
+ {
+ dprintf(CRITICAL, "Failed to set power on WP for user\n");
+ return ret;
+ }
+
+ num_wp_grps = ROUNDUP(len, wp_grp_size) / wp_grp_size;
+
+ if (set_clr)
+ cmd.cmd_index = CMD28_SET_WRITE_PROTECT;
+ else
+ cmd.cmd_index = CMD29_CLEAR_WRITE_PROTECT;
+
+ cmd.cmd_type = SDHCI_CMD_TYPE_NORMAL;
+ cmd.resp_type = SDHCI_CMD_RESP_R1B;
+
+ for(i = 0; i < num_wp_grps; i++)
+ {
+ cmd.argument = addr + (i * wp_grp_size);
+
+ if (sdhci_send_command(&dev->host, &cmd))
+ return 1;
+
+ /* CMD28/CMD29 On failure returns address out of range error */
+ if (MMC_ADDR_OUT_OF_RANGE(cmd.resp[0]))
+ {
+ dprintf(CRITICAL, "Address for CMD28/29 is out of range\n");
+ return 1;
+ }
+
+ /* Check the card status */
+ do
+ {
+ if (mmc_get_card_status(&dev->host, &dev->card, &status))
+ {
+ dprintf(CRITICAL, "Failed to get card status afterapplying write protect\n");
+ return 1;
+ }
+
+ /* Time out for WP command */
+ retry++;
+ udelay(1000);
+ if (retry == MMC_MAX_CARD_STAT_RETRY)
+ {
+ dprintf(CRITICAL, "Card status timed out after sending write protect command\n");
+ return 1;
+ }
+ } while (!(status & MMC_READY_FOR_DATA) || (MMC_CARD_STATUS(status) == MMC_PROG_STATE));
+
+ }
+
+ return 0;
+}