From patchwork Wed Feb 28 15:47:21 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ludovic BARRE X-Patchwork-Id: 10248293 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 6C99160211 for ; Wed, 28 Feb 2018 15:58:37 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 55CCC28D6A for ; Wed, 28 Feb 2018 15:58:37 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 49A2828DB7; Wed, 28 Feb 2018 15:58:37 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-1.9 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID autolearn=unavailable version=3.3.1 Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id C980528DB6 for ; Wed, 28 Feb 2018 15:58:35 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:Cc:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=zoHYBqlEbSVm/nVzzjxPe1LnMwGl3avI4REMs8cKsAE=; b=nB8L2Y94p8zTS+ baGfiJv/3rgCC8Kwylp+8WEOuFBg5kEiZQ85PjwPKwQyrb4Pp5qmXCWaBKir1pDm/znckJPolUJug Pg1RBJ8Fg5Lgdj8OeC4EDl4XEmCwFHRXFv6dnBTsEkgqQ91FBI5xjn0BxtVqdLJiOe8A4JFYDnoSi uUPMQXb5socLc5Je6FKNumrhEYwi9We6aPV++KOLu8dD4YkidsNHGWuWcEiGcxIc4oJIWSxa2CkfN YNC3rO0Hf/B1drqt8EwTnFggCboX9RjcMndgeJlfbc7lfBDv6M5JLSviw8+cgMsyyMrh0/Wlb/S82 A7hD4tHWZhQnUkqF5OBw==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.89 #1 (Red Hat Linux)) id 1er47Z-0001Bf-1t; Wed, 28 Feb 2018 15:58:21 +0000 Received: from mx08-00178001.pphosted.com ([91.207.212.93] helo=mx07-00178001.pphosted.com) by bombadil.infradead.org with esmtps (Exim 4.89 #1 (Red Hat Linux)) id 1er3xJ-0001KQ-NC for linux-arm-kernel@lists.infradead.org; Wed, 28 Feb 2018 15:48:03 +0000 Received: from pps.filterd (m0046660.ppops.net [127.0.0.1]) by mx08-.pphosted.com (8.16.0.21/8.16.0.21) with SMTP id w1SFiD0m025715; Wed, 28 Feb 2018 16:47:30 +0100 Received: from beta.dmz-eu.st.com (beta.dmz-eu.st.com [164.129.1.35]) by mx08-00178001.pphosted.com with ESMTP id 2gax28n7ec-1 (version=TLSv1 cipher=ECDHE-RSA-AES256-SHA bits=256 verify=NOT); Wed, 28 Feb 2018 16:47:30 +0100 Received: from zeta.dmz-eu.st.com (zeta.dmz-eu.st.com [164.129.230.9]) by beta.dmz-eu.st.com (STMicroelectronics) with ESMTP id 6447031; Wed, 28 Feb 2018 15:47:29 +0000 (GMT) Received: from Webmail-eu.st.com (Safex1hubcas22.st.com [10.75.90.92]) by zeta.dmz-eu.st.com (STMicroelectronics) with ESMTP id 33E185008; Wed, 28 Feb 2018 15:47:29 +0000 (GMT) Received: from SAFEX1HUBCAS23.st.com (10.75.90.47) by Safex1hubcas22.st.com (10.75.90.92) with Microsoft SMTP Server (TLS) id 14.3.352.0; Wed, 28 Feb 2018 16:47:29 +0100 Received: from lmecxl0923.lme.st.com (10.48.0.237) by webmail-ga.st.com (10.75.90.48) with Microsoft SMTP Server (TLS) id 14.3.361.1; Wed, 28 Feb 2018 16:47:28 +0100 From: Ludovic Barre To: Ulf Hansson , Rob Herring Subject: [PATCH V2 2/5] mmc: add stm32 sdmmc controller driver Date: Wed, 28 Feb 2018 16:47:21 +0100 Message-ID: <1519832844-28068-3-git-send-email-ludovic.Barre@st.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1519832844-28068-1-git-send-email-ludovic.Barre@st.com> References: <1519832844-28068-1-git-send-email-ludovic.Barre@st.com> MIME-Version: 1.0 X-Originating-IP: [10.48.0.237] X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10432:, , definitions=2018-02-28_08:, , signatures=0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20180228_074746_197600_55949DFF X-CRM114-Status: GOOD ( 21.78 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: devicetree@vger.kernel.org, Alexandre Torgue , linux-mmc@vger.kernel.org, linux-kernel@vger.kernel.org, Ludovic Barre , Maxime Coquelin , Gerald Baeza , linux-arm-kernel@lists.infradead.org Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Virus-Scanned: ClamAV using ClamSMTP From: Ludovic Barre This patch adds support for stm32 SDMMC controller. The SDMMC controller provides an interface for SD,SDIO cards and MMC devices. The SDMMC features include the following: -Full compliance with MultiMediaCard System Specification Version 4.51. Card support for three different databus modes: 1-bit (default), 4-bit and 8-bit. -Full compliance with SD memory card specifications version 4.1. SDR104 speed limited to maximum allowed I/O speed, SPI mode and UHS-II mode not supported. -Full compliance with SDIO card specification version 4.0. Signed-off-by: Ludovic Barre --- drivers/mmc/host/Kconfig | 8 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/stm32-sdmmc.c | 708 +++++++++++++++++++++++++++++++++++++++++ drivers/mmc/host/stm32-sdmmc.h | 220 +++++++++++++ 4 files changed, 937 insertions(+) create mode 100644 drivers/mmc/host/stm32-sdmmc.c create mode 100644 drivers/mmc/host/stm32-sdmmc.h diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 620c2d9..13c4d32 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -938,3 +938,11 @@ config MMC_SDHCI_OMAP If you have a controller with this interface, say Y or M here. If unsure, say N. + +config MMC_STM32_SDMMC + tristate "STMicroelectronics STM32 SD/MMC Host Controller support" + depends on ARCH_STM32 && OF + help + This selects support for the SD/MMC controller on STM32 SoCs. + If you have a board based on such a SoC and with a SD/MMC slot, + say Y or M here. diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 84cd138..13f601f 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_MMC_SUNXI) += sunxi-mmc.o obj-$(CONFIG_MMC_USDHI6ROL0) += usdhi6rol0.o obj-$(CONFIG_MMC_TOSHIBA_PCI) += toshsd.o obj-$(CONFIG_MMC_BCM2835) += bcm2835.o +obj-$(CONFIG_MMC_STM32_SDMMC) += stm32-sdmmc.o obj-$(CONFIG_MMC_REALTEK_PCI) += rtsx_pci_sdmmc.o obj-$(CONFIG_MMC_REALTEK_USB) += rtsx_usb_sdmmc.o diff --git a/drivers/mmc/host/stm32-sdmmc.c b/drivers/mmc/host/stm32-sdmmc.c new file mode 100644 index 0000000..8705e25 --- /dev/null +++ b/drivers/mmc/host/stm32-sdmmc.c @@ -0,0 +1,708 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) STMicroelectronics 2018 - All Rights Reserved + * Author: Ludovic Barre for STMicroelectronics. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "stm32-sdmmc.h" + +#define DRIVER_NAME "stm32-sdmmc" + +#ifdef CONFIG_DEBUG_FS +static int stm32_sdmmc_stat_show(struct seq_file *s, void *v) +{ + struct sdmmc_host *host = s->private; + struct sdmmc_stat *stat = &host->stat; + + seq_puts(s, "\033[1;34mstm32 sdmmc statistic\033[0m\n"); + seq_printf(s, "%-20s:%d\n", "sdmmc ck", host->sdmmc_ck); + seq_printf(s, "%-20s:%ld\n", "nb request", stat->n_req); + seq_printf(s, "%-20s:%ld\n", "nb data req", stat->n_datareq); + seq_printf(s, "%-20s:%ld\n", "nb cmd timeout", stat->n_ctimeout); + seq_printf(s, "%-20s:%ld\n", "nb cmd crcfail", stat->n_ccrcfail); + seq_printf(s, "%-20s:%ld\n", "nb dat timeout", stat->n_dtimeout); + seq_printf(s, "%-20s:%ld\n", "nb dat crcfail", stat->n_dcrcfail); + seq_printf(s, "%-20s:%ld\n", "nb rx overrun", stat->n_rxoverrun); + seq_printf(s, "%-20s:%ld\n", "nb tx underrun", stat->n_txunderrun); + + return 0; +} + +static ssize_t stm32_sdmmc_stat_reset(struct file *filp, + const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct seq_file *seqf = filp->private_data; + struct sdmmc_host *host = seqf->private; + + mutex_lock(&seqf->lock); + memset(&host->stat, 0, sizeof(host->stat)); + mutex_unlock(&seqf->lock); + + return count; +} + +static int stm32_sdmmc_stat_open(struct inode *inode, struct file *file) +{ + return single_open(file, stm32_sdmmc_stat_show, inode->i_private); +} + +static const struct file_operations stm32_sdmmc_stat_fops = { + .owner = THIS_MODULE, + .open = stm32_sdmmc_stat_open, + .read = seq_read, + .write = stm32_sdmmc_stat_reset, + .llseek = seq_lseek, + .release = single_release, +}; + +static void stm32_sdmmc_stat_init(struct sdmmc_host *host) +{ + struct mmc_host *mmc = host->mmc; + struct dentry *root; + + root = mmc->debugfs_root; + if (!root) + return; + + if (!debugfs_create_file("stat", 0600, root, host, + &stm32_sdmmc_stat_fops)) + dev_err(mmc_dev(host->mmc), "failed to initialize debugfs\n"); +} + +#define STAT_INC(stat) ((stat)++) +#else +static void stm32_sdmmc_stat_init(struct sdmmc_host *host) +{ +} + +#define STAT_INC(stat) +#endif + +static inline void enable_imask(struct sdmmc_host *host, u32 imask) +{ + u32 newmask; + + newmask = readl_relaxed(host->base + SDMMC_MASKR); + newmask |= imask; + + dev_vdbg(mmc_dev(host->mmc), "mask:%#x\n", newmask); + + writel_relaxed(newmask, host->base + SDMMC_MASKR); +} + +static inline void disable_imask(struct sdmmc_host *host, u32 imask) +{ + u32 newmask; + + newmask = readl_relaxed(host->base + SDMMC_MASKR); + newmask &= ~imask; + + dev_vdbg(mmc_dev(host->mmc), "mask:%#x\n", newmask); + + writel_relaxed(newmask, host->base + SDMMC_MASKR); +} + +static inline void clear_imask(struct sdmmc_host *host) +{ + u32 mask = readl_relaxed(host->base + SDMMC_MASKR); + + /* preserve the SDIO IRQ mask state */ + mask &= MASKR_SDIOITIE; + + dev_vdbg(mmc_dev(host->mmc), "mask:%#x\n", mask); + + writel_relaxed(mask, host->base + SDMMC_MASKR); +} + +static int stm32_sdmmc_card_busy(struct mmc_host *mmc) +{ + struct sdmmc_host *host = mmc_priv(mmc); + unsigned long flags; + u32 status; + + spin_lock_irqsave(&host->lock, flags); + status = readl_relaxed(host->base + SDMMC_STAR); + spin_unlock_irqrestore(&host->lock, flags); + + return !!(status & STAR_BUSYD0); +} + +static void stm32_sdmmc_request_end(struct sdmmc_host *host, + struct mmc_request *mrq) +{ + writel_relaxed(0, host->base + SDMMC_CMDR); + writel_relaxed(ICR_STATIC_FLAG, host->base + SDMMC_ICR); + + host->mrq = NULL; + host->cmd = NULL; + host->data = NULL; + + clear_imask(host); + + mmc_request_done(host->mmc, mrq); +} + +static void stm32_sdmmc_pwroff(struct sdmmc_host *host) +{ + /* Only a reset could disable sdmmc */ + reset_control_assert(host->rst); + udelay(2); + reset_control_deassert(host->rst); + + /* + * Set the SDMMC in Power-cycle state. This will make that the + * SDMMC_D[7:0], SDMMC_CMD and SDMMC_CK are driven low, + * to prevent the Card from being powered through the signal lines. + */ + writel_relaxed(POWERCTRL_CYC | host->pwr_reg_add, + host->base + SDMMC_POWER); +} + +static void stm32_sdmmc_pwron(struct sdmmc_host *host) +{ + /* + * After a power-cycle state, we must set the SDMMC in Power-off. + * The SDMMC_D[7:0], SDMMC_CMD and SDMMC_CK are driven high. + * Then we can set the SDMMC to Power-on state + */ + writel_relaxed(POWERCTRL_OFF | host->pwr_reg_add, + host->base + SDMMC_POWER); + mdelay(1); + writel_relaxed(POWERCTRL_ON | host->pwr_reg_add, + host->base + SDMMC_POWER); +} + +static void stm32_sdmmc_set_clkreg(struct sdmmc_host *host, struct mmc_ios *ios) +{ + u32 desired = ios->clock; + u32 clk = 0; + + /* + * sdmmc_ck = sdmmcclk/(2*clkdiv) + * clkdiv 0 => bypass + */ + if (desired) { + if (desired >= host->sdmmcclk) { + clk = 0; + host->sdmmc_ck = host->sdmmcclk; + } else { + clk = DIV_ROUND_UP(host->sdmmcclk, 2 * desired); + if (clk > CLKCR_CLKDIV_MAX) { + clk = CLKCR_CLKDIV_MAX; + dev_warn(mmc_dev(host->mmc), + "divisor too short, sd clk over clocked\n"); + } + + host->sdmmc_ck = host->sdmmcclk / (2 * clk); + } + } + + if (ios->bus_width == MMC_BUS_WIDTH_4) + clk |= CLKCR_WIDBUS_4; + if (ios->bus_width == MMC_BUS_WIDTH_8) + clk |= CLKCR_WIDBUS_8; + + clk |= CLKCR_HWFC_EN; + + writel_relaxed(clk | host->clk_reg_add, host->base + SDMMC_CLKCR); +} + +static void stm32_sdmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct sdmmc_host *host = mmc_priv(mmc); + + stm32_sdmmc_set_clkreg(host, ios); + + switch (ios->power_mode) { + case MMC_POWER_OFF: + if (!IS_ERR(mmc->supply.vmmc)) + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0); + + stm32_sdmmc_pwroff(host); + return; + case MMC_POWER_UP: + if (!IS_ERR(mmc->supply.vmmc)) + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, ios->vdd); + break; + case MMC_POWER_ON: + stm32_sdmmc_pwron(host); + break; + } +} + +static int stm32_sdmmc_validate_data(struct sdmmc_host *host, + struct mmc_data *data, int cookie) +{ + int n_elem; + + if (!data || data->host_cookie == COOKIE_PRE_MAPPED) + return 0; + + if (!is_power_of_2(data->blksz)) { + dev_err(mmc_dev(host->mmc), + "unsupported block size (%d bytes)\n", data->blksz); + return -EINVAL; + } + + if (data->sg->offset & 3 || data->sg->length & 3) { + dev_err(mmc_dev(host->mmc), + "unaligned scatterlist: ofst:%x length:%d\n", + data->sg->offset, data->sg->length); + return -EINVAL; + } + + n_elem = dma_map_sg(mmc_dev(host->mmc), + data->sg, + data->sg_len, + mmc_get_dma_dir(data)); + + if (!n_elem) { + dev_err(mmc_dev(host->mmc), "dma_map_sg failed\n"); + return -EINVAL; + } + + data->host_cookie = cookie; + + return 0; +} + +static void stm32_sdmmc_start_data(struct sdmmc_host *host, + struct mmc_data *data) +{ + u32 datactrl, timeout, imask, idmactrl; + unsigned long long clks; + + dev_dbg(mmc_dev(host->mmc), "blksz %d blks %d flags %08x\n", + data->blksz, data->blocks, data->flags); + + STAT_INC(host->stat.n_datareq); + host->data = data; + host->size = data->blksz * data->blocks; + data->bytes_xfered = 0; + + clks = (unsigned long long)data->timeout_ns * host->sdmmc_ck; + do_div(clks, NSEC_PER_SEC); + timeout = data->timeout_clks + (unsigned int)clks; + + writel_relaxed(timeout, host->base + SDMMC_DTIMER); + writel_relaxed(host->size, host->base + SDMMC_DLENR); + + datactrl = FIELD_PREP(DCTRLR_DBLOCKSIZE_MASK, ilog2(data->blksz)); + + if (data->flags & MMC_DATA_READ) { + datactrl |= DCTRLR_DTDIR; + imask = MASKR_RXOVERRIE; + } else { + imask = MASKR_TXUNDERRIE; + } + + if (host->mmc->card && mmc_card_sdio(host->mmc->card)) + datactrl |= DCTRLR_SDIOEN | DCTRLR_DTMODE_SDIO; + + idmactrl = IDMACTRLR_IDMAEN; + + writel_relaxed(sg_dma_address(data->sg), + host->base + SDMMC_IDMABASE0R); + writel_relaxed(idmactrl, host->base + SDMMC_IDMACTRLR); + + imask |= MASKR_DATAENDIE | MASKR_DTIMEOUTIE | MASKR_DCRCFAILIE; + enable_imask(host, imask); + + writel_relaxed(datactrl, host->base + SDMMC_DCTRLR); +} + +static void stm32_sdmmc_start_cmd(struct sdmmc_host *host, + struct mmc_command *cmd, u32 c) +{ + u32 imsk; + + dev_dbg(mmc_dev(host->mmc), "op %u arg %08x flags %08x\n", + cmd->opcode, cmd->arg, cmd->flags); + + STAT_INC(host->stat.n_req); + + if (readl_relaxed(host->base + SDMMC_CMDR) & CMDR_CPSMEM) + writel_relaxed(0, host->base + SDMMC_CMDR); + + c |= cmd->opcode | CMDR_CPSMEM; + if (cmd->flags & MMC_RSP_PRESENT) { + imsk = MASKR_CMDRENDIE | MASKR_CTIMEOUTIE; + if (cmd->flags & MMC_RSP_CRC) + imsk |= MASKR_CCRCFAILIE; + + if (cmd->flags & MMC_RSP_136) + c |= CMDR_WAITRESP_LRSP_CRC; + else if (cmd->flags & MMC_RSP_CRC) + c |= CMDR_WAITRESP_SRSP_CRC; + else + c |= CMDR_WAITRESP_SRSP; + } else { + c &= ~CMDR_WAITRESP_MASK; + imsk = MASKR_CMDSENTIE; + } + + host->cmd = cmd; + + enable_imask(host, imsk); + + writel_relaxed(cmd->arg, host->base + SDMMC_ARGR); + writel_relaxed(c, host->base + SDMMC_CMDR); +} + +static void stm32_sdmmc_cmd_irq(struct sdmmc_host *host, u32 status) +{ + struct mmc_command *cmd = host->cmd; + + if (!cmd) + return; + + host->cmd = NULL; + + if (status & STAR_CTIMEOUT) { + STAT_INC(host->stat.n_ctimeout); + cmd->error = -ETIMEDOUT; + host->dpsm_abort = true; + } else if (status & STAR_CCRCFAIL && cmd->flags & MMC_RSP_CRC) { + STAT_INC(host->stat.n_ccrcfail); + cmd->error = -EILSEQ; + host->dpsm_abort = true; + } else if (status & STAR_CMDREND && cmd->flags & MMC_RSP_PRESENT) { + cmd->resp[0] = readl_relaxed(host->base + SDMMC_RESP1R); + cmd->resp[1] = readl_relaxed(host->base + SDMMC_RESP2R); + cmd->resp[2] = readl_relaxed(host->base + SDMMC_RESP3R); + cmd->resp[3] = readl_relaxed(host->base + SDMMC_RESP4R); + } + + if (!host->data) + stm32_sdmmc_request_end(host, host->mrq); +} + +static void stm32_sdmmc_data_irq(struct sdmmc_host *host, u32 status) +{ + struct mmc_data *data = host->data; + struct mmc_command *stop = &host->stop_abort; + + if (!data) + return; + + if (status & STAR_DCRCFAIL) { + STAT_INC(host->stat.n_dcrcfail); + data->error = -EILSEQ; + if (readl_relaxed(host->base + SDMMC_DCNTR)) + host->dpsm_abort = true; + } else if (status & STAR_DTIMEOUT) { + STAT_INC(host->stat.n_dtimeout); + data->error = -ETIMEDOUT; + host->dpsm_abort = true; + } else if (status & STAR_TXUNDERR) { + STAT_INC(host->stat.n_txunderrun); + data->error = -EIO; + host->dpsm_abort = true; + } else if (status & STAR_RXOVERR) { + STAT_INC(host->stat.n_rxoverrun); + data->error = -EIO; + host->dpsm_abort = true; + } + + if (status & STAR_DATAEND || data->error || host->dpsm_abort) { + host->data = NULL; + + writel_relaxed(0, host->base + SDMMC_IDMACTRLR); + + if (!data->error) + data->bytes_xfered = data->blocks * data->blksz; + + /* + * To stop Data Path State Machine, a stop_transmission command + * shall be send on cmd or data errors of single, multi, + * pre-defined block and stream request. + */ + if (host->dpsm_abort && !data->stop) { + memset(stop, 0, sizeof(struct mmc_command)); + stop->opcode = MMC_STOP_TRANSMISSION; + stop->arg = 0; + stop->flags = MMC_RSP_R1B | MMC_CMD_AC; + data->stop = stop; + } + + disable_imask(host, MASKR_RXOVERRIE | MASKR_TXUNDERRIE + | MASKR_DCRCFAILIE | MASKR_DATAENDIE + | MASKR_DTIMEOUTIE); + + if (!data->stop) + stm32_sdmmc_request_end(host, data->mrq); + else + stm32_sdmmc_start_cmd(host, data->stop, CMDR_CMDSTOP); + } +} + +static irqreturn_t stm32_sdmmc_irq(int irq, void *dev_id) +{ + struct sdmmc_host *host = dev_id; + u32 status; + + spin_lock(&host->lock); + + status = readl_relaxed(host->base + SDMMC_STAR); + dev_dbg(mmc_dev(host->mmc), "irq sta:%#x\n", status); + writel_relaxed(status & ICR_STATIC_FLAG, host->base + SDMMC_ICR); + + stm32_sdmmc_cmd_irq(host, status); + stm32_sdmmc_data_irq(host, status); + + spin_unlock(&host->lock); + + return IRQ_HANDLED; +} + +static void stm32_sdmmc_pre_req(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct sdmmc_host *host = mmc_priv(mmc); + struct mmc_data *data = mrq->data; + + if (!data) + return; + + /* This data might be unmapped at this time */ + data->host_cookie = COOKIE_UNMAPPED; + + if (!stm32_sdmmc_validate_data(host, mrq->data, COOKIE_PRE_MAPPED)) + data->host_cookie = COOKIE_UNMAPPED; +} + +static void stm32_sdmmc_post_req(struct mmc_host *mmc, struct mmc_request *mrq, + int err) +{ + struct sdmmc_host *host = mmc_priv(mmc); + struct mmc_data *data = mrq->data; + + if (!data) + return; + + if (data->host_cookie != COOKIE_UNMAPPED) + dma_unmap_sg(mmc_dev(host->mmc), + data->sg, + data->sg_len, + mmc_get_dma_dir(data)); + + data->host_cookie = COOKIE_UNMAPPED; +} + +static void stm32_sdmmc_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + unsigned int cmdat = 0; + struct sdmmc_host *host = mmc_priv(mmc); + unsigned long flags; + + mrq->cmd->error = stm32_sdmmc_validate_data(host, mrq->data, + COOKIE_MAPPED); + if (mrq->cmd->error) { + mmc_request_done(mmc, mrq); + return; + } + + spin_lock_irqsave(&host->lock, flags); + + host->mrq = mrq; + + if (mrq->data) { + host->dpsm_abort = false; + stm32_sdmmc_start_data(host, mrq->data); + cmdat |= CMDR_CMDTRANS; + } + + stm32_sdmmc_start_cmd(host, mrq->cmd, cmdat); + + spin_unlock_irqrestore(&host->lock, flags); +} + +static struct mmc_host_ops stm32_sdmmc_ops = { + .request = stm32_sdmmc_request, + .pre_req = stm32_sdmmc_pre_req, + .post_req = stm32_sdmmc_post_req, + .set_ios = stm32_sdmmc_set_ios, + .get_cd = mmc_gpio_get_cd, + .card_busy = stm32_sdmmc_card_busy, +}; + +static const struct of_device_id stm32_sdmmc_match[] = { + { .compatible = "st,stm32h7-sdmmc",}, + {}, +}; +MODULE_DEVICE_TABLE(of, stm32_sdmmc_match); + +static int stm32_sdmmc_of_parse(struct mmc_host *mmc) +{ + struct sdmmc_host *host = mmc_priv(mmc); + int ret = mmc_of_parse(mmc); + + if (ret) + return ret; + + if (device_property_present(mmc_dev(mmc),"st,neg-edge")) + host->clk_reg_add |= CLKCR_NEGEDGE; + if (device_property_present(mmc_dev(mmc), "st,dir-output-high")) + host->pwr_reg_add |= POWER_DIRPOL; + if (device_property_present(mmc_dev(mmc), "st,use-ckin")) + host->clk_reg_add |= CLKCR_SELCLKRX_CKIN; + + return 0; +} + +static int stm32_sdmmc_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct sdmmc_host *host; + struct mmc_host *mmc; + struct resource *res; + int irq, ret; + + if (!np) { + dev_err(&pdev->dev, "No DT found\n"); + return -EINVAL; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return -EINVAL; + + mmc = mmc_alloc_host(sizeof(struct sdmmc_host), &pdev->dev); + if (!mmc) + return -ENOMEM; + + host = mmc_priv(mmc); + host->mmc = mmc; + platform_set_drvdata(pdev, mmc); + + host->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(host->base)) { + ret = PTR_ERR(host->base); + goto host_free; + } + + writel_relaxed(0, host->base + SDMMC_MASKR); + writel_relaxed(~0UL, host->base + SDMMC_ICR); + + ret = devm_request_irq(&pdev->dev, irq, stm32_sdmmc_irq, IRQF_SHARED, + DRIVER_NAME " (cmd)", host); + if (ret) + goto host_free; + + host->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(host->clk)) { + ret = PTR_ERR(host->clk); + goto host_free; + } + + ret = clk_prepare_enable(host->clk); + if (ret) + goto host_free; + + host->sdmmcclk = clk_get_rate(host->clk); + mmc->f_min = DIV_ROUND_UP(host->sdmmcclk, 2 * CLKCR_CLKDIV_MAX); + mmc->f_max = host->sdmmcclk; + + ret = stm32_sdmmc_of_parse(mmc); + if (ret) + goto clk_disable; + + host->rst = devm_reset_control_get(&pdev->dev, NULL); + if (IS_ERR(host->rst)) { + ret = PTR_ERR(host->rst); + goto clk_disable; + } + + stm32_sdmmc_pwroff(host); + + /* Get regulators and the supported OCR mask */ + ret = mmc_regulator_get_supply(mmc); + if (ret == -EPROBE_DEFER) + goto clk_disable; + + if (!mmc->ocr_avail) + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + + mmc->ops = &stm32_sdmmc_ops; + + /* IDMA cannot do scatter lists */ + mmc->max_segs = 1; + mmc->max_req_size = DLENR_DATALENGHT_MAX; + mmc->max_seg_size = mmc->max_req_size; + mmc->max_blk_size = 1 << DCTRLR_DBLOCKSIZE_MAX; + + /* + * Limit the number of blocks transferred so that we don't overflow + * the maximum request size. + */ + mmc->max_blk_count = mmc->max_req_size >> DCTRLR_DBLOCKSIZE_MAX; + + spin_lock_init(&host->lock); + + ret = mmc_add_host(mmc); + if (ret) + goto clk_disable; + + stm32_sdmmc_stat_init(host); + + host->ip_ver = readl_relaxed(host->base + SDMMC_IPVR); + dev_info(&pdev->dev, "%s: rev:%ld.%ld irq:%d\n", + mmc_hostname(mmc), + FIELD_GET(IPVR_MAJREV_MASK, host->ip_ver), + FIELD_GET(IPVR_MINREV_MASK, host->ip_ver), irq); + + return 0; + +clk_disable: + clk_disable_unprepare(host->clk); +host_free: + mmc_free_host(mmc); + return ret; +} + +static int stm32_sdmmc_remove(struct platform_device *pdev) +{ + struct mmc_host *mmc = platform_get_drvdata(pdev); + struct sdmmc_host *host = mmc_priv(mmc); + + /* Debugfs stuff is cleaned up by mmc core */ + mmc_remove_host(mmc); + clk_disable_unprepare(host->clk); + mmc_free_host(mmc); + + return 0; +} + +static struct platform_driver stm32_sdmmc_driver = { + .probe = stm32_sdmmc_probe, + .remove = stm32_sdmmc_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = stm32_sdmmc_match, + }, +}; + +module_platform_driver(stm32_sdmmc_driver); + +MODULE_DESCRIPTION("STMicroelectronics STM32 MMC/SD Card Interface driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Ludovic Barre "); diff --git a/drivers/mmc/host/stm32-sdmmc.h b/drivers/mmc/host/stm32-sdmmc.h new file mode 100644 index 0000000..e39578e --- /dev/null +++ b/drivers/mmc/host/stm32-sdmmc.h @@ -0,0 +1,220 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) STMicroelectronics 2018 - All Rights Reserved + * Author: Ludovic Barre for STMicroelectronics. + */ +#define SDMMC_POWER 0x000 +#define POWERCTRL_MASK GENMASK(1, 0) +#define POWERCTRL_OFF 0x00 +#define POWERCTRL_CYC 0x02 +#define POWERCTRL_ON 0x03 +#define POWER_VSWITCH BIT(2) +#define POWER_VSWITCHEN BIT(3) +#define POWER_DIRPOL BIT(4) + +#define SDMMC_CLKCR 0x004 +#define CLKCR_CLKDIV_MASK GENMASK(9, 0) +#define CLKCR_CLKDIV_MAX CLKCR_CLKDIV_MASK +#define CLKCR_PWRSAV BIT(12) +#define CLKCR_WIDBUS_4 BIT(14) +#define CLKCR_WIDBUS_8 BIT(15) +#define CLKCR_NEGEDGE BIT(16) +#define CLKCR_HWFC_EN BIT(17) +#define CLKCR_DDR BIT(18) +#define CLKCR_BUSSPEED BIT(19) +#define CLKCR_SELCLKRX_MASK GENMASK(21, 20) +#define CLKCR_SELCLKRX_CK (0 << 20) +#define CLKCR_SELCLKRX_CKIN (1 << 20) +#define CLKCR_SELCLKRX_FBCK (2 << 20) + +#define SDMMC_ARGR 0x008 + +#define SDMMC_CMDR 0x00c +#define CMDR_CMDTRANS BIT(6) +#define CMDR_CMDSTOP BIT(7) +#define CMDR_WAITRESP_MASK GENMASK(9, 8) +#define CMDR_WAITRESP_NORSP (0 << 8) +#define CMDR_WAITRESP_SRSP_CRC (1 << 8) +#define CMDR_WAITRESP_SRSP (2 << 8) +#define CMDR_WAITRESP_LRSP_CRC (3 << 8) +#define CMDR_WAITINT BIT(10) +#define CMDR_WAITPEND BIT(11) +#define CMDR_CPSMEM BIT(12) +#define CMDR_DTHOLD BIT(13) +#define CMDR_BOOTMODE BIT(14) +#define CMDR_BOOTEN BIT(15) +#define CMDR_CMDSUSPEND BIT(16) + +#define SDMMC_RESPCMDR 0x010 +#define SDMMC_RESP1R 0x014 +#define SDMMC_RESP2R 0x018 +#define SDMMC_RESP3R 0x01c +#define SDMMC_RESP4R 0x020 + +#define SDMMC_DTIMER 0x024 + +#define SDMMC_DLENR 0x028 +#define DLENR_DATALENGHT_MASK GENMASK(24, 0) +#define DLENR_DATALENGHT_MAX DLENR_DATALENGHT_MASK + +#define SDMMC_DCTRLR 0x02c +#define DCTRLR_DTEN BIT(0) +#define DCTRLR_DTDIR BIT(1) +#define DCTRLR_DTMODE_MASK GENMASK(3, 2) +#define DCTRLR_DTMODE_BLOCK (0 << 2) +#define DCTRLR_DTMODE_SDIO (1 << 2) +#define DCTRLR_DTMODE_MMC (2 << 2) +#define DCTRLR_DBLOCKSIZE_MASK GENMASK(7, 4) +#define DCTRLR_DBLOCKSIZE_MAX 14 +#define DCTRLR_RWSTART BIT(8) +#define DCTRLR_RWSTOP BIT(9) +#define DCTRLR_RWMOD BIT(10) +#define DCTRLR_SDIOEN BIT(11) +#define DCTRLR_BOOTACKEN BIT(12) +#define DCTRLR_FIFORST BIT(13) + +#define SDMMC_DCNTR 0x030 + +#define SDMMC_STAR 0x034 +#define STAR_CCRCFAIL BIT(0) +#define STAR_DCRCFAIL BIT(1) +#define STAR_CTIMEOUT BIT(2) +#define STAR_DTIMEOUT BIT(3) +#define STAR_TXUNDERR BIT(4) +#define STAR_RXOVERR BIT(5) +#define STAR_CMDREND BIT(6) +#define STAR_CMDSENT BIT(7) +#define STAR_DATAEND BIT(8) +#define STAR_DHOLD BIT(9) +#define STAR_DBCKEND BIT(10) +#define STAR_DABORT BIT(11) +#define STAR_DPSMACT BIT(12) +#define STAR_CPSMACT BIT(13) +#define STAR_TXFIFOHE BIT(14) +#define STAR_TXFIFOHF BIT(15) +#define STAR_TXFIFOF BIT(16) +#define STAR_RXFIFOF BIT(17) +#define STAR_TXFIFOE BIT(18) +#define STAR_RXFIFOE BIT(19) +#define STAR_BUSYD0 BIT(20) +#define STAR_BUSYD0END BIT(21) +#define STAR_SDIOIT BIT(22) +#define STAR_ACKFAIL BIT(23) +#define STAR_ACKTIMEOUT BIT(24) +#define STAR_VSWEND BIT(25) +#define STAR_CKSTOP BIT(26) +#define STAR_IDMATE BIT(27) +#define STAR_IDMABTC BIT(28) + +#define SDMMC_ICR 0x038 +#define ICR_CCRCFAILC BIT(0) +#define ICR_DCRCFAILC BIT(1) +#define ICR_CTIMEOUTC BIT(2) +#define ICR_DTIMEOUTC BIT(3) +#define ICR_TXUNDERRC BIT(4) +#define ICR_RXOVERRC BIT(5) +#define ICR_CMDRENDC BIT(6) +#define ICR_CMDSENTC BIT(7) +#define ICR_DATAENDC BIT(8) +#define ICR_DHOLDC BIT(9) +#define ICR_DBCKENDC BIT(10) +#define ICR_DABORTC BIT(11) +#define ICR_BUSYD0ENDC BIT(21) +#define ICR_SDIOITC BIT(22) +#define ICR_ACKFAILC BIT(23) +#define ICR_ACKTIMEOUTC BIT(24) +#define ICR_VSWENDC BIT(25) +#define ICR_CKSTOPC BIT(26) +#define ICR_IDMATEC BIT(27) +#define ICR_IDMABTCC BIT(28) +#define ICR_STATIC_FLAG ((GENMASK(28, 21)) | (GENMASK(11, 0))) + +#define SDMMC_MASKR 0x03c +#define MASKR_CCRCFAILIE BIT(0) +#define MASKR_DCRCFAILIE BIT(1) +#define MASKR_CTIMEOUTIE BIT(2) +#define MASKR_DTIMEOUTIE BIT(3) +#define MASKR_TXUNDERRIE BIT(4) +#define MASKR_RXOVERRIE BIT(5) +#define MASKR_CMDRENDIE BIT(6) +#define MASKR_CMDSENTIE BIT(7) +#define MASKR_DATAENDIE BIT(8) +#define MASKR_DHOLDIE BIT(9) +#define MASKR_DBCKENDIE BIT(10) +#define MASKR_DABORTIE BIT(11) +#define MASKR_TXFIFOHEIE BIT(14) +#define MASKR_RXFIFOHFIE BIT(15) +#define MASKR_RXFIFOFIE BIT(17) +#define MASKR_TXFIFOEIE BIT(18) +#define MASKR_BUSYD0ENDIE BIT(21) +#define MASKR_SDIOITIE BIT(22) +#define MASKR_ACKFAILIE BIT(23) +#define MASKR_ACKTIMEOUTIE BIT(24) +#define MASKR_VSWENDIE BIT(25) +#define MASKR_CKSTOPIE BIT(26) +#define MASKR_IDMABTCIE BIT(28) + +#define SDMMC_ACKTIMER 0x040 +#define ACKTIMER_ACKTIME_MASK GENMASK(24, 0) + +#define SDMMC_FIFOR 0x080 + +#define SDMMC_IDMACTRLR 0x050 +#define IDMACTRLR_IDMAEN BIT(0) +#define IDMACTRLR_IDMABMODE BIT(1) +#define IDMACTRLR_IDMABACT BIT(2) + +#define SDMMC_IDMABSIZER 0x054 +#define IDMABSIZER_IDMABNDT_MASK GENMASK(12, 5) + +#define SDMMC_IDMABASE0R 0x058 +#define SDMMC_IDMABASE1R 0x05c + +#define SDMMC_IPVR 0x3fc +#define IPVR_MINREV_MASK GENMASK(3, 0) +#define IPVR_MAJREV_MASK GENMASK(7, 4) + +enum stm32_sdmmc_cookie { + COOKIE_UNMAPPED, + COOKIE_PRE_MAPPED, /* mapped by pre_req() of stm32 */ + COOKIE_MAPPED, /* mapped by prepare_data() of stm32 */ +}; + +struct sdmmc_stat { + unsigned long n_req; + unsigned long n_datareq; + unsigned long n_ctimeout; + unsigned long n_ccrcfail; + unsigned long n_dtimeout; + unsigned long n_dcrcfail; + unsigned long n_txunderrun; + unsigned long n_rxoverrun; + unsigned long nb_dma_err; +}; + +struct sdmmc_host { + void __iomem *base; + struct mmc_host *mmc; + struct clk *clk; + struct reset_control *rst; + + u32 clk_reg_add; + u32 pwr_reg_add; + + struct mmc_request *mrq; + struct mmc_command *cmd; + struct mmc_data *data; + struct mmc_command stop_abort; + bool dpsm_abort; + + /* protect host registers access */ + spinlock_t lock; + + unsigned int sdmmcclk; + unsigned int sdmmc_ck; + + u32 size; + + u32 ip_ver; + struct sdmmc_stat stat; +};