From patchwork Sat Feb 27 18:01:46 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Carlo Caione X-Patchwork-Id: 8444841 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 5C6B99F2F0 for ; Sat, 27 Feb 2016 18:04:57 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id CF2C2203C4 for ; Sat, 27 Feb 2016 18:04:55 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 41FED203C3 for ; Sat, 27 Feb 2016 18:04:54 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1aZjD2-0002XR-7u; Sat, 27 Feb 2016 18:03:16 +0000 Received: from mail-wm0-x243.google.com ([2a00:1450:400c:c09::243]) by bombadil.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1aZjCM-0001rw-JR for linux-arm-kernel@lists.infradead.org; Sat, 27 Feb 2016 18:02:38 +0000 Received: by mail-wm0-x243.google.com with SMTP id p65so3417937wmp.1 for ; Sat, 27 Feb 2016 10:02:14 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=sender:from:to:cc:subject:date:message-id:in-reply-to:references; bh=PmkOPxOi8tKTrYFOzn+bsaOIidCHzmJXMknJ5G2i12o=; b=JknE0l3MAVurvwq4r42jQrx7jQIOui2HvaiPOO+/c5pNKvutiqg9ALd/mlg5zX3j/a cg5vlwGs+xXmU1uunEW0ZS2rXBD9Q+Sn3LqxCkfyqw6NiQmApd0JBaYHhKm/rBsPIbcv xzGA8Gn3vJeJJcw5R/NEnkxGDEHQBj0J6ekue683T6RIWWH5zmSSZ1dJXb99ozv4jXwH WXO8yGxOMT71cnSaOo+3RB/wDj4xcGVbYdr5wgy5VM3VAETstpjwZALHeTH5wA1Iu1XQ SgWwg/axDTKsoV/Lkm0E/xob3FwUEOZiCIVH6qCjPjUTyrqwJovRK4XA/Z1rmVh/aDfV uE7A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:sender:from:to:cc:subject:date:message-id :in-reply-to:references; bh=PmkOPxOi8tKTrYFOzn+bsaOIidCHzmJXMknJ5G2i12o=; b=f8RDSTGMYUx1Jd+U0MTjEC8K9TmFfrbhQjH5fa+IhbACiPMsmLdJG9iB+Oy49vS34G ezPiZvhhr9v9i3hbABzNaPqKK+80Lhk6f9AijzS1hA+U8+YGXePNIoeqCV/4RzIrYLKx TyEexsyAZA/qwl8kp4KOSORR8EPWR/TrOTmJPZVEmjp22+HfrIq4bLB/6N/7+GLnPxYQ ilID2R8ngn+BtoEdLm3s/7AtI9yTigdrucKR/oFM9QLT3uskSoC8RGHLJqHDB75tdMXG z/KXT0r4OEdujsMGuZ1jEyokWYGNaHmJlwyHrqsn9Oy5M61Sa93Gm8w9PoHAbNecz8RR jKqA== X-Gm-Message-State: AD7BkJKC1kfw3o5cHgdiX53ZqRYB8uvxlssrMO8p77q/PXkDjOSzN3DTRksrfAQghQokQg== X-Received: by 10.28.148.207 with SMTP id w198mr697801wmd.66.1456596132980; Sat, 27 Feb 2016 10:02:12 -0800 (PST) Received: from localhost.localdomain (2-238-57-164.ip242.fastwebnet.it. [2.238.57.164]) by smtp.gmail.com with ESMTPSA id gg7sm17680003wjd.10.2016.02.27.10.02.11 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Sat, 27 Feb 2016 10:02:11 -0800 (PST) From: Carlo Caione To: ulf.hansson@linaro.org, robh+dt@kernel.org, linux-arm-kernel@lists.infradead.org, linux@arm.linux.org.uk, devicetree@vger.kernel.org, linux-meson@googlegroups.com, linux@endlessm.com Subject: [PATCH v5 2/4] mmc: meson: Add driver for the SD/MMC host found on Amlogic Meson SoCs Date: Sat, 27 Feb 2016 19:01:46 +0100 Message-Id: <1456596108-1406-3-git-send-email-carlo@caione.org> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1456596108-1406-1-git-send-email-carlo@caione.org> References: <1456596108-1406-1-git-send-email-carlo@caione.org> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20160227_100235_060995_7971CBD5 X-CRM114-Status: GOOD ( 25.13 ) X-Spam-Score: -2.4 (--) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Carlo Caione MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-4.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_MED,RP_MATCHES_RCVD,T_DKIM_INVALID,UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Carlo Caione Add a driver for the SD/MMC host found on the Amlogic Meson SoCs. This is an MMC controller which provides an interface between the application processor and various memory cards. It supports the SD specification v2.0 and the eMMC specification v4.41. Signed-off-by: Carlo Caione --- Ulf, I addressed all your suggestions but the command timeout is still not implemented. I'm having some trouble trying to figure out how the timeout registers really work and how to integrate this hw timeout in the driver. This is not really a problem for the platforms we currently support and the driver works fairly well already. --- drivers/mmc/host/Kconfig | 7 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/meson-mmc.c | 535 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 543 insertions(+) create mode 100644 drivers/mmc/host/meson-mmc.c diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 1526b8a..99e0a0d 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -766,6 +766,13 @@ config MMC_REALTEK_USB Say Y here to include driver code to support SD/MMC card interface of Realtek RTS5129/39 series card reader +config MMC_MESON + tristate "Amlogic Meson SD/MMC Host Controller support" + depends on ARCH_MESON + help + This selects support for the SD/MMC Host Controller on + Amlogic Meson SoCs. + config MMC_SUNXI tristate "Allwinner sunxi SD/MMC Host Controller support" depends on ARCH_SUNXI diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 3595f83..26ddf76 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -54,6 +54,7 @@ obj-$(CONFIG_MMC_VUB300) += vub300.o obj-$(CONFIG_MMC_USHC) += ushc.o obj-$(CONFIG_MMC_WMT) += wmt-sdmmc.o obj-$(CONFIG_MMC_MOXART) += moxart-mmc.o +obj-$(CONFIG_MMC_MESON) += meson-mmc.o obj-$(CONFIG_MMC_SUNXI) += sunxi-mmc.o obj-$(CONFIG_MMC_USDHI6ROL0) += usdhi6rol0.o obj-$(CONFIG_MMC_TOSHIBA_PCI) += toshsd.o diff --git a/drivers/mmc/host/meson-mmc.c b/drivers/mmc/host/meson-mmc.c new file mode 100644 index 0000000..45b13fe --- /dev/null +++ b/drivers/mmc/host/meson-mmc.c @@ -0,0 +1,535 @@ +/* + * meson-mmc.c - Meson SDH Controller + * + * Copyright (C) 2015 Endless Mobile, Inc. + * Author: Carlo Caione + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#define SDIO_ARGU (0x00) +#define SDIO_SEND (0x04) +#define SDIO_CONF (0x08) +#define SDIO_IRQS (0x0c) +#define SDIO_IRQC (0x10) +#define SDIO_MULT (0x14) +#define SDIO_ADDR (0x18) +#define SDIO_EXT (0x1c) + +#define REG_IRQS_RESP_CRC7 BIT(5) +#define REG_IRQS_RD_CRC16 BIT(6) +#define REG_IRQS_WR_CRC16 BIT(7) +#define REG_IRQS_CMD_INT BIT(9) + +#define REG_IRQC_ARC_CMD_INT BIT(4) + +#define REG_CONF_CLK_DIV_M (0x3ff) +#define REG_CONF_BUS_WIDTH BIT(20) + +#define REG_MULT_PORT_SEL_M (0x3) +#define REG_MULT_RD_INDEX_M (0x0f) +#define REG_MULT_RD_INDEX_S (12) +#define REG_MULT_WR_RD_OUT_IND BIT(8) + +#define REG_SEND_CMD_COMMAND_M (0xff) +#define REG_SEND_CMD_RESP_S (8) +#define REG_SEND_RESP_NO_CRC7 BIT(16) +#define REG_SEND_RESP_HAVE_DATA BIT(17) +#define REG_SEND_RESP_CRC7_F_8 BIT(18) +#define REG_SEND_CHECK_BUSY_D0 BIT(19) +#define REG_SEND_CMD_SEND_DATA BIT(20) +#define REG_SEND_REP_PACK_N_M (0xff) +#define REG_SEND_REP_PACK_N_S (24) + +#define REG_EXT_DAT_RW_NUM_M (0x3fff) +#define REG_EXT_DAT_RW_NUM_S (16) + +#define SDIO_BOUNCE_REQ_SIZE (128 * 1024) + +struct meson_mmc_host { + struct mmc_host *mmc; + struct mmc_request *mrq; + struct delayed_work timeout_work; + struct clk *clk; + spinlock_t lock; + void __iomem *base; + long timeout; + int irq; + int error; + unsigned int port; + bool cmd_is_stop; + bool dying; +}; + +static int meson_mmc_clk_set_rate(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct meson_mmc_host *host = mmc_priv(mmc); + unsigned long clk_rate; + unsigned int clk_ios = ios->clock; + unsigned int clk_div; + u32 conf_reg; + + clk_rate = clk_get_rate(host->clk); + if (clk_rate < 0) { + dev_err(mmc_dev(mmc), "cannot get clock rate\n"); + return -EINVAL; + } + + clk_rate >>= 1; + clk_div = clk_rate / clk_ios - !(clk_rate % clk_ios); + + conf_reg = readl(host->base + SDIO_CONF); + conf_reg &= ~REG_CONF_CLK_DIV_M; + conf_reg |= clk_div; + writel(conf_reg, host->base + SDIO_CONF); + + host->mmc->actual_clock = clk_rate / ((clk_div + 1) * 2); + dev_dbg(mmc_dev(mmc), "clk_ios: %d, clk_div: %d, clk_rate: %ld\n", + clk_ios, clk_div, clk_rate); + + return 0; +} + +static void meson_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct meson_mmc_host *host = mmc_priv(mmc); + u32 reg; + + reg = readl(host->base + SDIO_CONF); + + switch (ios->bus_width) { + case MMC_BUS_WIDTH_1: + reg &= ~REG_CONF_BUS_WIDTH; + break; + case MMC_BUS_WIDTH_4: + reg |= REG_CONF_BUS_WIDTH; + break; + case MMC_BUS_WIDTH_8: + default: + dev_err(mmc_dev(mmc), "meson controller doesn't support 8bit data bus\n"); + host->error = -EINVAL; + return; + } + + writel(reg, host->base + SDIO_CONF); + + if (ios->clock && ios->power_mode) + host->error = meson_mmc_clk_set_rate(mmc, ios); +} + +static void meson_mmc_start_cmd(struct mmc_host *mmc, + struct mmc_command *cmd) +{ + struct meson_mmc_host *host = mmc_priv(mmc); + unsigned int pack_size; + u32 irqc, irqs, mult; + u32 send = 0; + u32 ext = 0; + + switch (mmc_resp_type(cmd)) { + case MMC_RSP_R1: + case MMC_RSP_R1B: + case MMC_RSP_R3: + send |= (45 << REG_SEND_CMD_RESP_S); + break; + case MMC_RSP_R2: + send |= (133 << REG_SEND_CMD_RESP_S); + send |= REG_SEND_RESP_CRC7_F_8; + break; + default: + break; + } + + if (!(cmd->flags & MMC_RSP_CRC)) + send |= REG_SEND_RESP_NO_CRC7; + + if (cmd->flags & MMC_RSP_BUSY) + send |= REG_SEND_CHECK_BUSY_D0; + + if (cmd->data) { + send &= ~(REG_SEND_REP_PACK_N_M << REG_SEND_REP_PACK_N_S); + send |= ((cmd->data->blocks - 1) << REG_SEND_REP_PACK_N_S); + + ext &= ~(REG_EXT_DAT_RW_NUM_M << REG_EXT_DAT_RW_NUM_S); + if (mmc->ios.bus_width) + pack_size = cmd->data->blksz * 8 + (16 - 1) * 4; + else + pack_size = cmd->data->blksz * 8 + (16 - 1); + ext |= (pack_size << REG_EXT_DAT_RW_NUM_S); + + if (cmd->data->flags & MMC_DATA_WRITE) + send |= REG_SEND_CMD_SEND_DATA; + else + send |= REG_SEND_RESP_HAVE_DATA; + } + + send &= ~REG_SEND_CMD_COMMAND_M; + send |= (0x40 | cmd->opcode); + + irqc = readl(host->base + SDIO_IRQC); + irqc |= REG_IRQC_ARC_CMD_INT; + + irqs = readl(host->base + SDIO_IRQS); + irqs |= REG_IRQS_CMD_INT; + + mult = readl(host->base + SDIO_MULT); + mult &= ~REG_MULT_PORT_SEL_M; + mult |= host->port; + mult |= (1 << 31); + writel(mult, host->base + SDIO_MULT); + writel(irqs, host->base + SDIO_IRQS); + writel(irqc, host->base + SDIO_IRQC); + + writel(cmd->arg, host->base + SDIO_ARGU); + writel(ext, host->base + SDIO_EXT); + writel(send, host->base + SDIO_SEND); +} + +static int meson_mmc_map_dma(struct meson_mmc_host *host, + struct mmc_data *data, + unsigned int flags) +{ u32 dma_len; + struct scatterlist *sg = data->sg; + + if (sg->offset & 3 || sg->length & 3) { + dev_err(mmc_dev(host->mmc), + "unaligned scatterlist: os %x length %d\n", + sg->offset, sg->length); + return -EINVAL; + } + + dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len, + ((data->flags & MMC_DATA_READ) ? + DMA_FROM_DEVICE : DMA_TO_DEVICE)); + if (dma_len == 0) { + dev_err(mmc_dev(host->mmc), "dma_map_sg failed\n"); + return -ENOMEM; + } + + return 0; +} + +static void meson_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct meson_mmc_host *host = mmc_priv(mmc); + struct mmc_command *cmd = mrq->cmd; + struct mmc_data *data = mrq->data; + unsigned long flags; + int ret; + + spin_lock_irqsave(&host->lock, flags); + + if (host->error) { + cmd->error = host->error; + spin_unlock_irqrestore(&host->lock, flags); + mmc_request_done(mmc, mrq); + return; + } + + if (data) { + ret = meson_mmc_map_dma(host, data, data->flags); + if (ret < 0) { + dev_err(mmc_dev(mmc), "map DMA failed\n"); + cmd->error = ret; + data->error = ret; + spin_unlock_irqrestore(&host->lock, flags); + mmc_request_done(mmc, mrq); + return; + } + writel(sg_dma_address(data->sg), host->base + SDIO_ADDR); + } + + host->mrq = mrq; + meson_mmc_start_cmd(mmc, mrq->cmd); + + spin_unlock_irqrestore(&host->lock, flags); + + schedule_delayed_work(&host->timeout_work, host->timeout); +} + +static irqreturn_t meson_mmc_irq(int irq, void *data) +{ + struct meson_mmc_host *host = (void *) data; + struct mmc_request *mrq = host->mrq; + u32 irqs; + + irqs = readl(host->base + SDIO_IRQS); + if (mrq && (irqs & REG_IRQS_CMD_INT)) + return IRQ_WAKE_THREAD; + + return IRQ_HANDLED; +} + +void meson_mmc_read_response(struct meson_mmc_host *host) +{ + struct mmc_command *cmd = host->mrq->cmd; + u32 mult; + int i, resp[4] = { 0 }; + + mult = readl(host->base + SDIO_MULT); + mult |= REG_MULT_WR_RD_OUT_IND; + mult &= ~(REG_MULT_RD_INDEX_M << REG_MULT_RD_INDEX_S); + writel(mult, host->base + SDIO_MULT); + + if (cmd->flags & MMC_RSP_136) { + for (i = 0; i <= 3; i++) + resp[3 - i] = readl(host->base + SDIO_ARGU); + cmd->resp[0] = (resp[0] << 8) | ((resp[1] >> 24) & 0xff); + cmd->resp[1] = (resp[1] << 8) | ((resp[2] >> 24) & 0xff); + cmd->resp[2] = (resp[2] << 8) | ((resp[3] >> 24) & 0xff); + cmd->resp[3] = (resp[3] << 8); + } else if (cmd->flags & MMC_RSP_PRESENT) { + cmd->resp[0] = readl(host->base + SDIO_ARGU); + } +} + +static irqreturn_t meson_mmc_irq_thread(int irq, void *irq_data) +{ + struct meson_mmc_host *host = (void *) irq_data; + struct mmc_data *data; + unsigned long flags; + struct mmc_request *mrq; + u32 irqs, send; + + cancel_delayed_work_sync(&host->timeout_work); + spin_lock_irqsave(&host->lock, flags); + + mrq = host->mrq; + data = mrq->data; + + if (!mrq) { + spin_unlock_irqrestore(&host->lock, flags); + return IRQ_HANDLED; + } + + if (host->cmd_is_stop) + goto out; + + irqs = readl(host->base + SDIO_IRQS); + send = readl(host->base + SDIO_SEND); + + mrq->cmd->error = 0; + + if (!data) { + if (!((irqs & REG_IRQS_RESP_CRC7) || + (send & REG_SEND_RESP_NO_CRC7))) + mrq->cmd->error = -EILSEQ; + else + meson_mmc_read_response(host); + } else { + if (!((irqs & REG_IRQS_RD_CRC16) || + (irqs & REG_IRQS_WR_CRC16))) { + mrq->cmd->error = -EILSEQ; + } else { + data->bytes_xfered = data->blksz * data->blocks; + dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, + ((data->flags & MMC_DATA_READ) ? + DMA_FROM_DEVICE : DMA_TO_DEVICE)); + } + } + + if (mrq->stop) { + host->cmd_is_stop = true; + meson_mmc_start_cmd(host->mmc, mrq->stop); + spin_unlock_irqrestore(&host->lock, flags); + return IRQ_HANDLED; + } + +out: + host->cmd_is_stop = false; + host->mrq = NULL; + spin_unlock_irqrestore(&host->lock, flags); + mmc_request_done(host->mmc, mrq); + + return IRQ_HANDLED; +} + +static void meson_mmc_timeout(struct work_struct *work) +{ + struct meson_mmc_host *host = container_of(work, + struct meson_mmc_host, + timeout_work.work); + struct mmc_request *mrq = host->mrq; + unsigned long flags; + u32 irqc; + + /* Do not run after meson_mmc_remove() */ + if (host->dying) + return; + + spin_lock_irqsave(&host->lock, flags); + + dev_err(mmc_dev(host->mmc), "Timeout on CMD%u\n", mrq->cmd->opcode); + + irqc = readl(host->base + SDIO_IRQC); + irqc &= ~REG_IRQC_ARC_CMD_INT; + writel(irqc, host->base + SDIO_IRQC); + + mrq->cmd->error = -ETIMEDOUT; + + host->mrq = NULL; + spin_unlock_irqrestore(&host->lock, flags); + + mmc_request_done(host->mmc, mrq); +} + +static struct mmc_host_ops meson_mmc_ops = { + .request = meson_mmc_request, + .set_ios = meson_mmc_set_ios, + .get_cd = mmc_gpio_get_cd, +}; + +static int meson_mmc_probe(struct platform_device *pdev) +{ + struct mmc_host *mmc; + struct meson_mmc_host *host; + struct pinctrl *pinctrl; + struct resource *res; + int ret, irq; + u32 port; + + mmc = mmc_alloc_host(sizeof(struct meson_mmc_host), &pdev->dev); + if (!mmc) { + dev_err(&pdev->dev, "mmc alloc host failed\n"); + return -ENOMEM; + } + + host = mmc_priv(mmc); + host->mmc = mmc; + host->timeout = msecs_to_jiffies(10000); + host->port = 0; + + if (!of_property_read_u32(pdev->dev.of_node, "meson,sd-port", &port)) + host->port = port; + + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + host->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(host->base)) { + ret = PTR_ERR(host->base); + goto error_free_host; + } + + irq = platform_get_irq(pdev, 0); + ret = devm_request_threaded_irq(&pdev->dev, irq, meson_mmc_irq, + meson_mmc_irq_thread, 0, "meson_mmc", + host); + if (ret) + goto error_free_host; + + host->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(host->clk)) { + ret = PTR_ERR(host->clk); + goto error_free_host; + } + + ret = clk_prepare_enable(host->clk); + if (ret) { + dev_err(&pdev->dev, "Enable clk error %d\n", ret); + goto error_free_host; + } + + /* we do not support scatter lists in hardware */ + mmc->max_segs = 1; + mmc->max_req_size = SDIO_BOUNCE_REQ_SIZE; + mmc->max_seg_size = mmc->max_req_size; + mmc->max_blk_count = 256; + mmc->max_blk_size = mmc->max_req_size / mmc->max_blk_count; + mmc->f_min = 300000; + mmc->f_max = 50000000; + mmc->caps |= MMC_CAP_4_BIT_DATA; + mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED; + mmc->caps2 |= MMC_CAP2_NO_SDIO; + mmc->ocr_avail = MMC_VDD_33_34; + mmc->ops = &meson_mmc_ops; + + spin_lock_init(&host->lock); + + INIT_DELAYED_WORK(&host->timeout_work, meson_mmc_timeout); + + pinctrl = devm_pinctrl_get(&pdev->dev); + if (IS_ERR(pinctrl)) { + ret = PTR_ERR(pinctrl); + goto error; + } + + ret = mmc_of_parse(mmc); + if (ret) + goto error; + + platform_set_drvdata(pdev, mmc); + + ret = mmc_add_host(mmc); + if (ret) + goto error; + + dev_info(&pdev->dev, "base:0x%p irq:%u port:%u\n", + host->base, irq, host->port); + + return 0; + +error: + clk_disable_unprepare(host->clk); +error_free_host: + mmc_free_host(mmc); + + return ret; +} + +static int meson_mmc_remove(struct platform_device *pdev) +{ + struct mmc_host *mmc = platform_get_drvdata(pdev); + struct meson_mmc_host *host = mmc_priv(mmc); + + host->dying = true; + + mmc_remove_host(mmc); + cancel_delayed_work_sync(&host->timeout_work); + clk_disable_unprepare(host->clk); + + mmc_free_host(mmc); + + return 0; +} + +static const struct of_device_id meson_mmc_of_match[] = { + { .compatible = "amlogic,meson-mmc", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, meson_mmc_of_match); + +static struct platform_driver meson_mmc_driver = { + .probe = meson_mmc_probe, + .remove = meson_mmc_remove, + .driver = { + .name = "meson-mmc", + .of_match_table = of_match_ptr(meson_mmc_of_match), + }, +}; + +module_platform_driver(meson_mmc_driver); + +MODULE_DESCRIPTION("Meson Secure Digital Host Driver"); +MODULE_AUTHOR("Carlo Caione "); +MODULE_LICENSE("GPL");