>From bc325f4abbcbd7e4032ce148d7a6850eb2a754c8 Mon Sep 17 00:00:00 2001
From: Yunpeng Gao <[email protected]>
Date: Fri, 17 Dec 2010 19:00:10 +0800
Subject: [PATCH] Implement the Medfield eMMC mutex (Dekker Algorithm) 
acquire/release APIs

Changes compared with the old implementation:
1. Remove the card re-init operation if last owner is SCU.
2. Add a usage counter to manage the acquire/release pair on IA side.

Signed-off-by: Yunpeng Gao <[email protected]>
---
 drivers/mmc/host/sdhci-pci.c |   74 +++++++++++++++++-
 drivers/mmc/host/sdhci.c     |  177 ++++++++++++++++++++++++++++++++++++++++++
 include/linux/mmc/host.h     |    5 +
 include/linux/mmc/sdhci.h    |   14 +++-
 4 files changed, 268 insertions(+), 2 deletions(-)

diff --git a/drivers/mmc/host/sdhci-pci.c b/drivers/mmc/host/sdhci-pci.c
index 30b1905..7158435 100644
--- a/drivers/mmc/host/sdhci-pci.c
+++ b/drivers/mmc/host/sdhci-pci.c
@@ -44,6 +44,8 @@
 
 #define MAX_SLOTS                      8
 
+#define IPC_EMMC_MUTEX_CMD             0xEE
+
 static DEFINE_MUTEX(port_mutex);
 
 struct sdhci_pci_chip;
@@ -184,6 +186,76 @@ static const struct sdhci_pci_fixes sdhci_intel_mfd_sd = {
                          SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC,
 };
 
+static int intel_mfld_emmc0_probe(struct sdhci_pci_chip *chip)
+{
+       if (chip->pdev->revision == 0) /* Penwell A0 */
+               chip->quirks |= SDHCI_QUIRK_MFD_EMMC_SDIO_RESTRICTION;
+       else if (chip->pdev->revision == 4) /* Penwell B0 */
+               /*
+                * The eMMC mutex (Dekker algorithm) support in SCU firmware
+                * is only available for Penwell B0.
+                */
+               chip->quirks |= SDHCI_QUIRK_NEED_DEKKER_MUTEX;
+
+       return 0;
+}
+
+/*
+ * Get the base address in shared SRAM for eMMC mutex
+ * (Dekker's algorithm) through IPC call.
+ *
+ * Please note it'll always return 0 whether the address requesting
+ * success or not. So, the mmc driver will still work well if the scu
+ * firmware is not ready yet.
+*/
+static int intel_mfld_emmc0_probe_slot(struct sdhci_pci_slot *slot)
+{
+       u32 mutex_var_addr[3];
+       int ret;
+
+       ret = intel_scu_ipc_command(IPC_EMMC_MUTEX_CMD, 0,
+                       NULL, 0, mutex_var_addr, 3);
+       if (ret) {
+               dev_err(&slot->chip->pdev->dev, "IPC error: %d\n", ret);
+               slot->host->sram_addr = 0;
+       } else {
+               /* 3 housekeeping mutex variables, 12 bytes length */
+               slot->host->sram_addr = ioremap_nocache(mutex_var_addr[0], 16);
+               if (!slot->host->sram_addr) {
+                       dev_err(&slot->chip->pdev->dev, "ioremap failed!\n");
+               } else {
+                       dev_info(&slot->chip->pdev->dev, "mapped addr: %p\n",
+                               slot->host->sram_addr);
+                       dev_info(&slot->chip->pdev->dev, "current eMMC owner:"
+                               " %d, IA req: %d, SCU req: %d\n",
+                               readl(slot->host->sram_addr +
+                                       DEKKER_EMMC_OWNER_OFFSET),
+                               readl(slot->host->sram_addr +
+                                       DEKKER_IA_REQ_OFFSET),
+                               readl(slot->host->sram_addr +
+                                       DEKKER_SCU_REQ_OFFSET));
+               }
+       }
+
+       return 0;
+}
+
+static void intel_mfld_emmc0_remove_slot(struct sdhci_pci_slot *slot, int dead)
+{
+       if (dead)
+               return;
+
+       if (slot->host->sram_addr)
+               iounmap(slot->host->sram_addr);
+}
+
+static const struct sdhci_pci_fixes sdhci_intel_mfld_emmc0 = {
+       .quirks         = SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC,
+       .probe          = intel_mfld_emmc0_probe,
+       .probe_slot     = intel_mfld_emmc0_probe_slot,
+       .remove_slot    = intel_mfld_emmc0_remove_slot,
+};
+
 static const struct sdhci_pci_fixes sdhci_intel_mfd_emmc_sdio = {
        .quirks         = SDHCI_QUIRK_MFD_EMMC_SDIO_RESTRICTION |
                          SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC,
@@ -583,7 +655,7 @@ static const struct pci_device_id pci_ids[] __devinitdata = 
{
                .device         = PCI_DEVICE_ID_INTEL_MFD_EMMC0,
                .subvendor      = PCI_ANY_ID,
                .subdevice      = PCI_ANY_ID,
-               .driver_data    = (kernel_ulong_t)&sdhci_intel_mfd_emmc_sdio,
+               .driver_data    = (kernel_ulong_t)&sdhci_intel_mfld_emmc0,
        },
 
        {
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index 7b9a3b3..1aedc3f 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1328,11 +1328,188 @@ out:
        spin_unlock_irqrestore(&host->lock, flags);
 }
 
+/*
+ * One of the Medfield eMMC controller (PCI device id 0x0823, SDIO3) is
+ * a shared resource used by the SCU and the IA processors. SCU primarily
+ * uses the eMMC host controller to access the eMMC device's Boot Partition,
+ * while the IA CPU uses the eMMC host controller to access the eMMC device's
+ * User Partition.
+ *
+ * After the SCU hands off the system to the IA processor, the IA processor
+ * assumes ownership to the eMMC host controller. Due to absence of any
+ * arbitration at the eMMC host controller, this could result in concurrent
+ * eMMC host accesses resulting in bus contention and garbage data ending up
+ * in either of the partitions.
+ * To circumvent this from happening, eMMC host controller locking mechanism
+ * is employed, where at any one given time, only one agent, SCU or IA, may be
+ * allowed to access the host. This is achieved by implementing Dekker's
+ * Algorithm (http://en.wikipedia.org/wiki/Dekker's_algorithm) between the
+ * two processors.
+ *
+ * Before handing off the system to the IA processor, SCU must set up three
+ * housekeeping mutex variables allocated in the shared SRAM as follows:
+ *
+ * eMMC_Owner = IA (SCU and IA processors - RW, 32bit)
+ * IA_Req = FALSE (IA -RW, SCU - RO, 32bit)
+ * SCU_Req = FALSE (IA - RO, SCU - R/W, 32bit)
+ *
+ * There is no hardware based access control to these variables and so code
+ * executing on SCU and IA processors must follow below access rules
+ * (Dekker's algorithm):
+ *
+ * -----------------------------------------
+ * SCU Processor Implementation
+ * -----------------------------------------
+ * SCU_Req = TRUE;
+ * while (IA_Req == TRUE) {
+ *     if (eMMC_Owner != SCU){
+ *         SCU_Req = FALSE;
+ *         while (eMMC_Owner != SCU);
+ *         SCU_Req = TRUE;
+ *     }
+ * }
+ * // SCU now performs eMMC transactions here
+ * ...
+ * // When done, relinquish control to IA
+ * eMMC_Owner = IA;
+ * SCU_Req = FALSE;
+ *
+ * -----------------------------------------
+ * IA Processor Implementation
+ * -----------------------------------------
+ * IA_Req = TRUE;
+ * while (SCU_Req == TRUE) {
+ *     if (eMMC_Owner != IA){
+ *         IA_Req = FALSE;
+ *         while (eMMC_Owner != IA);
+ *         IA_Req = TRUE;
+ *     }
+ * }
+ * //IA now performs eMMC transactions here
+ * ...
+ * //When done, relinquish control to SCU
+ * eMMC_Owner = SCU;
+ * IA_Req = FALSE;
+ *
+ * ----------------------------------------
+*/
+
+/* Implement the Dekker's algorithm on the IA processor side.
+ *
+ * Return value:
+ * 0 - Acquried the ownership successfully. The last owner is IA
+ * 1 - Acquried the ownership succesffully. The last owenr is SCU
+ * -EBUSY - failed to acquire ownership within the timeout period.
+ */
+static int sdhci_acquire_ownership(struct mmc_host *mmc)
+{
+       struct sdhci_host *host;
+       unsigned long t1, t2;
+
+       host = mmc_priv(mmc);
+
+       if (!((host->quirks & SDHCI_QUIRK_NEED_DEKKER_MUTEX) &&
+                               (host->sram_addr)))
+               return 0;
+
+       atomic_inc(&host->usage_cnt);
+
+       DBG("Acquire ownership - eMMC owner: %d, IA req: %d, SCU req: %d\n",
+               readl(host->sram_addr + DEKKER_EMMC_OWNER_OFFSET),
+               readl(host->sram_addr + DEKKER_IA_REQ_OFFSET),
+               readl(host->sram_addr + DEKKER_SCU_REQ_OFFSET));
+
+       writel(1, host->sram_addr + DEKKER_IA_REQ_OFFSET);
+
+       t1 = jiffies + 10 * HZ;
+       t2 = 500;
+
+       while (readl(host->sram_addr + DEKKER_SCU_REQ_OFFSET)) {
+               if (readl(host->sram_addr + DEKKER_EMMC_OWNER_OFFSET) !=
+                               DEKKER_OWNER_IA) {
+                       writel(0, host->sram_addr + DEKKER_IA_REQ_OFFSET);
+                       while (t2) {
+                               if (readl(host->sram_addr +
+                                       DEKKER_EMMC_OWNER_OFFSET) ==
+                                       DEKKER_OWNER_IA)
+                                       break;
+                               msleep(10);
+                               t2--;
+                       }
+                       if (t2) {
+                               writel(1, host->sram_addr +
+                                               DEKKER_IA_REQ_OFFSET);
+                       } else {
+                               pr_err("eMMC mutex timeout (owner)!\n");
+                               goto timeout;
+                       }
+               }
+               if (time_after(jiffies, t1)) {
+                       pr_err("eMMC mutex timeout (req)!\n");
+                       goto timeout;
+               }
+               cpu_relax();
+       }
+
+       if (readl(host->sram_addr + DEKKER_EMMC_OWNER_OFFSET) ==
+           DEKKER_OWNER_IA)
+               return 1; /* Tell caller to re-config the host controller */
+
+       return 0;
+timeout:
+       writel(DEKKER_OWNER_SCU, host->sram_addr + DEKKER_EMMC_OWNER_OFFSET);
+       writel(0, host->sram_addr + DEKKER_IA_REQ_OFFSET);
+       return -EBUSY;
+}
+
+static void sdhci_release_ownership(struct mmc_host *mmc)
+{
+       struct sdhci_host *host;
+
+       host = mmc_priv(mmc);
+
+       if (!((host->quirks & SDHCI_QUIRK_NEED_DEKKER_MUTEX) &&
+                               (host->sram_addr)))
+               return;
+
+       if (atomic_dec_and_test(&host->usage_cnt)) {
+               writel(DEKKER_OWNER_SCU,
+                      host->sram_addr + DEKKER_EMMC_OWNER_OFFSET);
+               writel(0, host->sram_addr + DEKKER_IA_REQ_OFFSET);
+               DBG("Exit ownership - "
+                   "eMMC owner: %d, IA req: %d, SCU req: %d\n",
+                   readl(host->sram_addr + DEKKER_EMMC_OWNER_OFFSET),
+                   readl(host->sram_addr + DEKKER_IA_REQ_OFFSET),
+                   readl(host->sram_addr + DEKKER_SCU_REQ_OFFSET));
+       }
+}
+
+/*
+ * Re-configure host controller registers here since SCU firmware
+ * has possibly changed some registers already
+ */
+static void sdhci_reconfig_host_controller(struct mmc_host *mmc)
+{
+       struct sdhci_host *host;
+
+       host = mmc_priv(mmc);
+
+       if (!((host->quirks & SDHCI_QUIRK_NEED_DEKKER_MUTEX) &&
+                               (host->sram_addr)))
+               return;
+
+       sdhci_init(host, 0);
+       sdhci_set_ios(host->mmc, &host->mmc->ios);
+}
+
 static const struct mmc_host_ops sdhci_ops = {
        .request        = sdhci_request,
        .set_ios        = sdhci_set_ios,
        .get_ro         = sdhci_get_ro,
        .enable_sdio_irq = sdhci_enable_sdio_irq,
+       .acquire_ownership = sdhci_acquire_ownership,
+       .release_ownership = sdhci_release_ownership,
+       .reconfig_host_controller = sdhci_reconfig_host_controller,
 };
 
 /*****************************************************************************\
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index 01e4886..abb2cab 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -117,6 +117,11 @@ struct mmc_host_ops {
 
        /* optional callback for HC quirks */
        void    (*init_card)(struct mmc_host *host, struct mmc_card *card);
+
+       /* optional callback for HC mutex (Dekker algorithm) */
+       int (*acquire_ownership)(struct mmc_host *host);
+       void (*release_ownership)(struct mmc_host *host);
+       void (*reconfig_host_controller)(struct mmc_host *host);
 };
 
 struct mmc_card;
diff --git a/include/linux/mmc/sdhci.h b/include/linux/mmc/sdhci.h
index dc712bb..5ae4eea 100644
--- a/include/linux/mmc/sdhci.h
+++ b/include/linux/mmc/sdhci.h
@@ -91,11 +91,23 @@ struct sdhci_host {
 /* Controller of Medfield specific restriction */
 #define SDHCI_QUIRK_MFD_SD_RESTRICTION                 (1ULL<<33)
 #define SDHCI_QUIRK_MFD_EMMC_SDIO_RESTRICTION          (1ULL<<34)
-
+/* One controller port will be accessed by driver and fw at the same time */
+#define SDHCI_QUIRK_NEED_DEKKER_MUTEX                  (1ULL<<35)
 
        int irq;                /* Device IRQ */
        void __iomem *ioaddr;   /* Mapped address */
 
+       /* XXX: SCU/X86 mutex variables base address in shared SRAM */
+       void __iomem *sram_addr;        /* Shared SRAM address */
+
+#define DEKKER_EMMC_OWNER_OFFSET       0
+#define DEKKER_IA_REQ_OFFSET           0x04
+#define DEKKER_SCU_REQ_OFFSET          0x08
+#define DEKKER_OWNER_IA                        0
+#define DEKKER_OWNER_SCU               1
+
+       atomic_t usage_cnt; /* eMMC mutex usage count */
+
        const struct sdhci_ops *ops;    /* Low level hw interface */
 
        struct regulator *vmmc; /* Power regulator */
-- 
1.5.4.5

_______________________________________________
MeeGo-kernel mailing list
[email protected]
http://lists.meego.com/listinfo/meego-kernel

Reply via email to