>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
