From: Kamal Dasu <[email protected]> Adding sdio rpmb partition sharing support in brcmstb host driver. The sdhci-brcmstb controller driver uses SDIO_SHARE work registers along with use of brcmstb hwspinlock framework to synchronize access between linux and trusted zone firmware. The sdhci-brcmstb driver claims and releases host for TZOS only when it is requested to do via ipi0 interrupt.
Signed-off-by: Kamal Dasu <[email protected]> --- drivers/mmc/host/sdhci-brcmstb.c | 243 ++++++++++++++++++++++++++++++- 1 file changed, 242 insertions(+), 1 deletion(-) diff --git a/drivers/mmc/host/sdhci-brcmstb.c b/drivers/mmc/host/sdhci-brcmstb.c index 48cdcba0f39c..dd68fd512459 100644 --- a/drivers/mmc/host/sdhci-brcmstb.c +++ b/drivers/mmc/host/sdhci-brcmstb.c @@ -12,6 +12,10 @@ #include <linux/of.h> #include <linux/bitops.h> #include <linux/delay.h> +#include <linux/hwspinlock.h> +#include <linux/workqueue.h> +#include <linux/kthread.h> +#include "../core/core.h" #include "sdhci-cqhci.h" #include "sdhci-pltfm.h" @@ -34,6 +38,11 @@ #define SDIO_CFG_CQ_CAPABILITY 0x4c #define SDIO_CFG_CQ_CAPABILITY_FMUL GENMASK(13, 12) +#define SDHCI_BRCMSTB_AGENT_LINUX 'L' +#define SDHCI_BRCMSTB_AGENT_TZOS 'A' +#define FLSHARE_IPIS0_INT_SEND_MASK BIT(17) +#define HWSPINLOCK_TIMEOUT_MS 100 + #define SDIO_CFG_CTRL 0x0 #define SDIO_CFG_CTRL_SDCD_N_TEST_EN BIT(31) #define SDIO_CFG_CTRL_SDCD_N_TEST_LEV BIT(30) @@ -46,9 +55,26 @@ /* Select all SD UHS type I SDR speed above 50MB/s */ #define MMC_CAP_UHS_I_SDR_MASK (MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104) +#define BRCMSTB_SD_SHARE_REG_NEXT 0x0 /* Next Agent Register */ +#define BRCMSTB_SD_SHARE_REG_PMC 0x4 /* Work Agent1 Register */ +#define BRCMSTB_SD_SHARE_REG_TZOS 0x8 /* Work Agent2 Register */ +#define BRCMSTB_SD_SHARE_REG_LINUX 0xc /* Work Agent3 Register */ + +struct brcmstb_sdio_share_info { + void __iomem *share_reg; + void __iomem *ipis0_reg; + struct hwspinlock *hwlock; + struct sdhci_host *host; + int irq_recv; + int host_claimed; + wait_queue_head_t wq; + struct task_struct *claim_thread; +}; + struct sdhci_brcmstb_priv { void __iomem *cfg_regs; unsigned int flags; + struct brcmstb_sdio_share_info *si; struct clk *base_clk; u32 base_freq_hz; }; @@ -303,6 +329,218 @@ static const struct of_device_id __maybe_unused sdhci_brcm_of_match[] = { {}, }; +static void sdhci_brcmstb_dump_shr_regs(struct sdhci_host *host) +{ + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_brcmstb_priv *priv = sdhci_pltfm_priv(pltfm_host); + struct brcmstb_sdio_share_info *si = priv->si; + + dev_dbg(mmc_dev(host->mmc), "wn:0x%x wa:0x%x wl:0x%x\n", + readl(si->share_reg + BRCMSTB_SD_SHARE_REG_NEXT), + readl(si->share_reg + BRCMSTB_SD_SHARE_REG_TZOS), + readl(si->share_reg + BRCMSTB_SD_SHARE_REG_LINUX)); +} + +static bool sdhci_brcmstb_linux_host_is_next(struct brcmstb_sdio_share_info *si) +{ + u32 wn; + + /* check if linux is next */ + wn = readl(si->share_reg + BRCMSTB_SD_SHARE_REG_NEXT); + return ((wn == SDHCI_BRCMSTB_AGENT_LINUX) ? true : false); +} + +static void sdhci_brcmstb_wait_for_linux_host(struct brcmstb_sdio_share_info *si) +{ + int ret; + + while (1) { + /* put self in wait queue when host not available */ + ret = wait_event_interruptible(si->wq, + sdhci_brcmstb_linux_host_is_next(si)); + if (ret != -ERESTARTSYS) + break; + }; +} + +static bool sdhci_brcmstb_tzos_is_waiting(struct brcmstb_sdio_share_info *si) +{ + u32 wt; + + /* check if TZOS has put itself in the work queue */ + wt = readl(si->share_reg + BRCMSTB_SD_SHARE_REG_TZOS); + return ((wt == SDHCI_BRCMSTB_AGENT_TZOS) ? true : false); +} + +static void sdhci_brcmstb_wait_for_tzos(struct brcmstb_sdio_share_info *si) +{ + int ret; + + while (1) { + /* wait in queue when tzos cannot use controller */ + ret = wait_event_interruptible(si->wq, + sdhci_brcmstb_tzos_is_waiting(si)); + if (ret != -ERESTARTSYS) + break; + } +} + +static void sdhci_brcmstb_aquire_hwsem(struct brcmstb_sdio_share_info *si) +{ + u32 wl = SDHCI_BRCMSTB_AGENT_LINUX; + struct mmc_host *mmc = si->host->mmc; + int ret; + + /* + * aquire hw sem : + * 1. write linux agent id to work register WL + * 2. Aquire hw semaphore + * 2. clear next work register WN + */ + writel(wl, si->share_reg + BRCMSTB_SD_SHARE_REG_LINUX); + /* try hw semaphore lock, we should never have to wait here */ + ret = hwspin_lock_timeout(si->hwlock, HWSPINLOCK_TIMEOUT_MS); + WARN_ON(ret != 0); + /* clear next register when holding the semaphore */ + writel(0, si->share_reg + BRCMSTB_SD_SHARE_REG_NEXT); + dev_dbg(mmc_dev(mmc), "hwsem aquire\n"); + sdhci_brcmstb_dump_shr_regs(si->host); +} + +static void sdhci_brcmstb_release_hwsem(struct brcmstb_sdio_share_info *si) +{ + u32 wt; + struct mmc_host *mmc = si->host->mmc; + int ret; + + /* + * release hw semphore + * 1. set the next work agent register WN before releasing hw sem + * 2. Release hw semaphore + * 3. send ipi to TZOS + */ + wt = readl(si->share_reg + BRCMSTB_SD_SHARE_REG_TZOS); + writel(wt, si->share_reg + BRCMSTB_SD_SHARE_REG_NEXT); + + /* release hw semaphore if we hold it and send IPI */ + ret = hwspin_trylock_raw(si->hwlock); + WARN_ON(ret != 0); + hwspin_unlock(si->hwlock); + + if (wt == SDHCI_BRCMSTB_AGENT_TZOS) + writel(FLSHARE_IPIS0_INT_SEND_MASK, si->ipis0_reg); + + dev_dbg(mmc_dev(mmc), "hwsem release\n"); + sdhci_brcmstb_dump_shr_regs(si->host); +} + +static irqreturn_t sdhci_brcmstb_recv_ipi0_irq(int irq, void *dev_id) +{ + struct sdhci_host *host = dev_id; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_brcmstb_priv *priv = sdhci_pltfm_priv(pltfm_host); + struct brcmstb_sdio_share_info *si = priv->si; + + dev_dbg(mmc_dev(host->mmc), "ipi irq %d next L:%d T:%d ch:%d\n", + irq, sdhci_brcmstb_linux_host_is_next(si), + sdhci_brcmstb_tzos_is_waiting(si), + si->host_claimed); + wake_up_interruptible(&si->wq); + return IRQ_HANDLED; +} + +static int sdhci_brcmstb_host_claim_thread(void *data) +{ + struct brcmstb_sdio_share_info *si = data; + struct mmc_host *mmc = si->host->mmc; + + do { + sdhci_brcmstb_wait_for_tzos(si); + /* claim host for TZOS */ + mmc_claim_host(mmc); + si->host_claimed += 1; + sdhci_brcmstb_release_hwsem(si); + dev_dbg(mmc_dev(mmc), "host claimed %d\n", si->host_claimed); + sdhci_brcmstb_wait_for_linux_host(si); + sdhci_brcmstb_aquire_hwsem(si); + /* release host */ + mmc_release_host(mmc); + si->host_claimed -= 1; + dev_dbg(mmc_dev(mmc), "host released %d\n", si->host_claimed); + } while (!kthread_should_stop()); + + return 0; +} + +static int sdhci_brcmstb_sdio_share_init(struct platform_device *pdev) +{ + struct sdhci_host *host = dev_get_drvdata(&pdev->dev); + struct device_node *np = pdev->dev.of_node; + struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host); + struct sdhci_brcmstb_priv *priv = sdhci_pltfm_priv(pltfm_host); + struct brcmstb_sdio_share_info *si; + void __iomem *sdio_sh_regs; + int ret; + + /* sdio_share block */ + sdio_sh_regs = devm_platform_ioremap_resource_byname(pdev, "share"); + if (IS_ERR(sdio_sh_regs)) + return 0; + + si = devm_kcalloc(&pdev->dev, 1, sizeof(struct brcmstb_sdio_share_info), + GFP_KERNEL); + if (!si) + return -ENOMEM; + + si->share_reg = sdio_sh_regs; + ret = of_hwspin_lock_get_id(np, 0); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get hwspinlock id %d\n", ret); + return ret; + } + + si->hwlock = devm_hwspin_lock_request_specific(&pdev->dev, ret); + if (!si->hwlock) { + dev_err(&pdev->dev, "failed to request hwspinlock\n"); + return -ENXIO; + } + + si->irq_recv = platform_get_irq_byname_optional(pdev, "recv_ipi0"); + if (si->irq_recv < 0) { + ret = si->irq_recv; + dev_err(&pdev->dev, "recv_ipi0 IRQ not found\n"); + return ret; + } + + ret = devm_request_irq(&pdev->dev, si->irq_recv, + sdhci_brcmstb_recv_ipi0_irq, + 0, "mmc_recv_ipi0", host); + if (ret < 0) { + dev_err(&pdev->dev, "mmc_recv_ipi0 IRQ request_irq failed\n"); + return ret; + } + + si->ipis0_reg = devm_platform_ioremap_resource_byname(pdev, "flshr_ipis0"); + if (IS_ERR(si->ipis0_reg)) + return -ENXIO; + + priv->si = si; + si->host = host; + init_waitqueue_head(&si->wq); + /* acquire hwsem */ + sdhci_brcmstb_aquire_hwsem(si); + si->claim_thread = + kthread_run(sdhci_brcmstb_host_claim_thread, si, + "ksdshrthread/%s", mmc_hostname(host->mmc)); + if (IS_ERR(si->claim_thread)) { + ret = PTR_ERR(si->claim_thread); + dev_err(&pdev->dev, "failed to run claim thread\n"); + return -ENOEXEC; + } + + return 0; +} + static u32 sdhci_brcmstb_cqhci_irq(struct sdhci_host *host, u32 intmask) { int cmd_error = 0; @@ -482,8 +720,11 @@ static int sdhci_brcmstb_probe(struct platform_device *pdev) goto err; pltfm_host->clk = clk; - return res; + res = sdhci_brcmstb_sdio_share_init(pdev); + if (res) + dev_warn(&pdev->dev, "sdio share unavailable\n"); + return 0; err: sdhci_pltfm_free(pdev); clk_disable_unprepare(base_clk); -- 2.34.1

