diff mbox

mmc: meson: Add driver for the SD/MMC host found on Amlogic MesonX SoCs

Message ID 1433783342-31243-1-git-send-email-carlo@caione.org (mailing list archive)
State New, archived
Headers show

Commit Message

Carlo Caione June 8, 2015, 5:09 p.m. UTC
From: Carlo Caione <carlo@endlessm.com>

Add a driver for the SD/MMC host found on the Amlogic MesonX SoCs. It
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. It also supports
SDSC, SDHC and SDXC memory card default speed.

This patch adds also the bindinding documentation.

Signed-off-by: Carlo Caione <carlo@endlessm.com>
---
 .../devicetree/bindings/mmc/meson-mmc.txt          |  38 ++
 drivers/mmc/host/Kconfig                           |   7 +
 drivers/mmc/host/Makefile                          |   1 +
 drivers/mmc/host/meson-mmc.c                       | 615 +++++++++++++++++++++
 4 files changed, 661 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/mmc/meson-mmc.txt
 create mode 100644 drivers/mmc/host/meson-mmc.c

Comments

Daniel Drake June 8, 2015, 11:15 p.m. UTC | #1
Hi Carlo,

On Mon, Jun 8, 2015 at 11:09 AM, Carlo Caione <carlo@caione.org> wrote:
> From: Carlo Caione <carlo@endlessm.com>
>
> Add a driver for the SD/MMC host found on the Amlogic MesonX SoCs. It
> 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. It also supports
> SDSC, SDHC and SDXC memory card default speed.

Nice work, thanks for this!

> This patch adds also the bindinding documentation.

"binding" typo

> +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;
> +
> +       spin_lock_irqsave(&host->lock, flags);
> +
> +       if (host->state != STATE_IDLE) {
> +               dev_dbg(mmc_dev(mmc), "%s() rejected, state %u\n",
> +                       __func__, host->state);
> +               mrq->cmd->error = -EAGAIN;
> +               spin_unlock_irqrestore(&host->lock, flags);
> +               mmc_request_done(mmc, mrq);
> +               return;
> +       }
> +
> +       if (host->ferror) {
> +               cmd->error = host->ferror;
> +               spin_unlock_irqrestore(&host->lock, flags);
> +               mmc_request_done(mmc, mrq);
> +               return;
> +       }
> +
> +       dev_dbg(mmc_dev(mmc), "CMD%d(%08x) arg %x len %d flags %08x\n",
> +               cmd->opcode & 0x3f, cmd->opcode, cmd->arg,
> +               mrq->data ? mrq->data->blksz * mrq->data->blocks : 0,
> +               mrq->cmd->flags);
> +
> +       /* Filter out CMD 5/52/53 */
> +       if (cmd->opcode == SD_IO_SEND_OP_COND ||
> +           cmd->opcode == SD_IO_RW_DIRECT ||
> +           cmd->opcode == SD_IO_RW_EXTENDED) {
> +               dev_dbg(mmc_dev(host->mmc), "CMD%d not supported\n", cmd->opcode);
> +               cmd->error = -EINVAL;
> +               spin_unlock_irqrestore(&host->lock, flags);
> +               mmc_request_done(mmc, mrq);
> +               return;
> +       }
> +
> +       host->state = STATE_REQUEST;
> +
> +       if (data) {
> +               meson_mmc_map_dma(host, data, data->flags);
> +               writel(host->sg_dma, host->base + SDIO_ADDR);

As the sg_dma buffer is constant, can you do this register write just
once at probe time? I wonder if we really need to do it on each
request.

Or, going deeper, I wonder if we can avoid this bounce buffer
entirely. Can you try setting mmc->max_segs to 1 then passing the
single data segment directly to the hardware with
   writel(sg_dma_address(data->sg), host->base + SDIO_ADDR)

If that turns out to be more complicated then it sounds, then feel
free to push it off to a later driver revision.

Thanks!
Daniel
Ulf Hansson June 9, 2015, 6:46 a.m. UTC | #2
On 8 June 2015 at 19:09, Carlo Caione <carlo@caione.org> wrote:
> From: Carlo Caione <carlo@endlessm.com>
>
> Add a driver for the SD/MMC host found on the Amlogic MesonX SoCs. It
> 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. It also supports
> SDSC, SDHC and SDXC memory card default speed.
>
> This patch adds also the bindinding documentation.
>
> Signed-off-by: Carlo Caione <carlo@endlessm.com>

Hi Carlo,

Please repost and add linux-mmc.

Kind regards
Uffe

> ---
>  .../devicetree/bindings/mmc/meson-mmc.txt          |  38 ++
>  drivers/mmc/host/Kconfig                           |   7 +
>  drivers/mmc/host/Makefile                          |   1 +
>  drivers/mmc/host/meson-mmc.c                       | 615 +++++++++++++++++++++
>  4 files changed, 661 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/mmc/meson-mmc.txt
>  create mode 100644 drivers/mmc/host/meson-mmc.c
>
> diff --git a/Documentation/devicetree/bindings/mmc/meson-mmc.txt b/Documentation/devicetree/bindings/mmc/meson-mmc.txt
> new file mode 100644
> index 0000000..839569a
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mmc/meson-mmc.txt
> @@ -0,0 +1,38 @@
> +* Amlogic MesonX MMC controller
> +
> +The highspeed MMC host controller on Amlogic SoCs provides an interface
> +for MMC, SD, SDIO and SDHC types of memory cards.
> +
> +Supported maximum speeds are the ones of the eMMC standard 4.41 as well
> +as the speed of SD standard 2.0.
> +
> +Required properties:
> + - compatible : "amlogic,meson-mmc"
> + - reg : mmc controller base registers
> + - interrupts : mmc controller interrupt
> + - clocks : phandle to SDIO clock provider
> +
> +Optional properties:
> + - meson,sdio-port : 0 for SDIO port A, 1 for SDIO port B. (default: 0)
> + - for cd, bus-width and additional generic mmc parameters
> +   please refer to mmc.txt within this directory
> +
> +Examples:
> +       - Within .dtsi:
> +       mmc0: mmc@c1108c20 {
> +               compatible = "amlogic,meson-mmc";
> +               reg = <0xc1108c20 0x20>;
> +               interrupts = <0 28 1>;
> +               clocks = <&clkc CLKID_CLK81>;
> +               status = "disabled";
> +       };
> +
> +       - Within .dts:
> +       &mmc0 {
> +               status = "okay";
> +               pinctrl-0 = <&mmc0_sd_b_pins>;
> +               pinctrl-names = "default";
> +               meson,sdio-port = <1>;
> +               cd-gpios = <&gpio CARD_6 0>;
> +               cd-inverted;
> +       };
> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
> index b1f837e..e61b8d6 100644
> --- a/drivers/mmc/host/Kconfig
> +++ b/drivers/mmc/host/Kconfig
> @@ -764,6 +764,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 MesonX SD/MMC Host Controller support"
> +       depends on ARCH_MESON
> +       help
> +         This selects support for the SD/MMC Host Controller on
> +         Amlogic MesonX 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 e3ab5b9..c0455e7 100644
> --- a/drivers/mmc/host/Makefile
> +++ b/drivers/mmc/host/Makefile
> @@ -53,6 +53,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..0bcae9d
> --- /dev/null
> +++ b/drivers/mmc/host/meson-mmc.c
> @@ -0,0 +1,615 @@
> +/*
> + * mesonsd.c - Meson SDH Controller
> + *
> + * Copyright (C) 2015 Endless Mobile, Inc.
> + *
> + * Licensed under the GPL-2 or later.
> + */
> +
> +#define DRIVER_NAME    "meson-mmc"
> +
> +#define DEBUG
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/ioport.h>
> +#include <linux/platform_device.h>
> +
> +#include <linux/mmc/host.h>
> +#include <linux/mmc/mmc.h>
> +#include <linux/mmc/sdio.h>
> +#include <linux/mmc/slot-gpio.h>
> +
> +#include <linux/of_address.h>
> +#include <linux/of_gpio.h>
> +#include <linux/of_platform.h>
> +
> +#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_IRQC_SOFT_RESET    BIT(15)
> +
> +#define REG_CONF_WR_CRC_S      (29)
> +#define REG_CONF_WR_NWR_S      (23)
> +#define REG_CONF_M_END_S       (21)
> +#define REG_CONF_ARGU_BITS_S   (12)
> +#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 CLK_DIV                        (0x1f4)
> +
> +#define SDIO_BOUNCE_REQ_SIZE   (128 * 1024)
> +
> +enum mmcif_state {
> +       STATE_IDLE,
> +       STATE_REQUEST,
> +       STATE_IOS,
> +       STATE_TIMEOUT,
> +       STATE_STOP,
> +};
> +
> +struct meson_mmc_host {
> +       struct mmc_host         *mmc;
> +       struct mmc_request      *mrq;
> +       struct delayed_work     timeout_work;
> +       struct clk              *clk_sdio;
> +       spinlock_t              lock;
> +       void __iomem            *base;
> +       void                    *sg_cpu;
> +       dma_addr_t              sg_dma;
> +       long                    timeout;
> +       int                     irq;
> +       int                     ferror;
> +       unsigned int            bus_width;
> +       unsigned int            port;
> +       enum mmcif_state        state;
> +};
> +
> +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_sdio) / 2;
> +       if (clk_rate < 0) {
> +               dev_err(mmc_dev(mmc), "cannot get clock rate\n");
> +               return -EINVAL;
> +       }
> +
> +       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);
> +
> +       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;
> +
> +       if (host->state != STATE_IDLE) {
> +               dev_dbg(mmc_dev(mmc), "%s() rejected, state %u\n",
> +                       __func__, host->state);
> +               return;
> +       }
> +
> +       host->state = STATE_IOS;
> +
> +       reg = readl(host->base + SDIO_CONF);
> +
> +       switch (ios->bus_width) {
> +       case MMC_BUS_WIDTH_1:
> +               host->bus_width = 0;
> +               reg &= ~REG_CONF_BUS_WIDTH;
> +               dev_dbg(mmc_dev(mmc), "bus width: 1bit\n");
> +               break;
> +       case MMC_BUS_WIDTH_4:
> +               host->bus_width = 1;
> +               reg |= REG_CONF_BUS_WIDTH;
> +               dev_dbg(mmc_dev(mmc), "bus width: 4bit\n");
> +               break;
> +       case MMC_BUS_WIDTH_8:
> +       default:
> +               dev_err(mmc_dev(mmc), "SDIO controller doesn't support 8bit data bus\n");
> +               host->ferror = -EINVAL;
> +               return;
> +       }
> +
> +       writel(reg, host->base + SDIO_CONF);
> +
> +       if (ios->clock && ios->power_mode)
> +               host->ferror = meson_mmc_clk_set_rate(mmc, ios);
> +
> +       host->state = STATE_IDLE;
> +}
> +
> +static void meson_mmc_soft_reset(struct meson_mmc_host *host)
> +{
> +       u32 irqc;
> +
> +       irqc = readl(host->base + SDIO_IRQC);
> +       irqc |= REG_IRQC_SOFT_RESET;
> +       writel(irqc, host->base + SDIO_IRQC);
> +       udelay(2);
> +}
> +
> +static void meson_mmc_start_cmd(struct mmc_host *mmc,
> +                               struct mmc_request *mrq)
> +{
> +       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(mrq->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 (!(mrq->cmd->flags & MMC_RSP_CRC))
> +               send |= REG_SEND_RESP_NO_CRC7;
> +
> +       if (mrq->cmd->flags & MMC_RSP_BUSY)
> +               send |= REG_SEND_CHECK_BUSY_D0;
> +
> +       if (mrq->data) {
> +               send &= ~(REG_SEND_REP_PACK_N_M << REG_SEND_REP_PACK_N_S);
> +               send |= ((mrq->data->blocks - 1) << REG_SEND_REP_PACK_N_S);
> +
> +               ext &= ~(REG_EXT_DAT_RW_NUM_M << REG_EXT_DAT_RW_NUM_S);
> +               if (host->bus_width)
> +                       pack_size = mrq->data->blksz * 8 + (16 - 1) * 4;
> +               else
> +                       pack_size = mrq->data->blksz * 8 + (16 - 1);
> +               ext |= (pack_size << REG_EXT_DAT_RW_NUM_S);
> +
> +               if (mrq->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 | mrq->cmd->opcode);
> +
> +       meson_mmc_soft_reset(host);
> +
> +       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(mrq->cmd->arg, host->base + SDIO_ARGU);
> +       writel(ext, host->base + SDIO_EXT);
> +       writel(send, host->base + SDIO_SEND);
> +}
> +
> +static void meson_mmc_map_dma(struct meson_mmc_host *host,
> +                             struct mmc_data *data,
> +                             unsigned int flags)
> +{
> +       size_t buflen = data->blksz * data->blocks;
> +
> +       if (flags & MMC_DATA_READ)
> +               sg_copy_from_buffer(data->sg, data->sg_len, host->sg_cpu, buflen);
> +       else
> +               sg_copy_to_buffer(data->sg, data->sg_len, host->sg_cpu, buflen);
> +}
> +
> +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;
> +
> +       spin_lock_irqsave(&host->lock, flags);
> +
> +       if (host->state != STATE_IDLE) {
> +               dev_dbg(mmc_dev(mmc), "%s() rejected, state %u\n",
> +                       __func__, host->state);
> +               mrq->cmd->error = -EAGAIN;
> +               spin_unlock_irqrestore(&host->lock, flags);
> +               mmc_request_done(mmc, mrq);
> +               return;
> +       }
> +
> +       if (host->ferror) {
> +               cmd->error = host->ferror;
> +               spin_unlock_irqrestore(&host->lock, flags);
> +               mmc_request_done(mmc, mrq);
> +               return;
> +       }
> +
> +       dev_dbg(mmc_dev(mmc), "CMD%d(%08x) arg %x len %d flags %08x\n",
> +               cmd->opcode & 0x3f, cmd->opcode, cmd->arg,
> +               mrq->data ? mrq->data->blksz * mrq->data->blocks : 0,
> +               mrq->cmd->flags);
> +
> +       /* Filter out CMD 5/52/53 */
> +       if (cmd->opcode == SD_IO_SEND_OP_COND ||
> +           cmd->opcode == SD_IO_RW_DIRECT ||
> +           cmd->opcode == SD_IO_RW_EXTENDED) {
> +               dev_dbg(mmc_dev(host->mmc), "CMD%d not supported\n", cmd->opcode);
> +               cmd->error = -EINVAL;
> +               spin_unlock_irqrestore(&host->lock, flags);
> +               mmc_request_done(mmc, mrq);
> +               return;
> +       }
> +
> +       host->state = STATE_REQUEST;
> +
> +       if (data) {
> +               meson_mmc_map_dma(host, data, data->flags);
> +               writel(host->sg_dma, host->base + SDIO_ADDR);
> +       }
> +
> +       host->mrq = mrq;
> +       schedule_delayed_work(&host->timeout_work, host->timeout);
> +       meson_mmc_start_cmd(mmc, mrq);
> +
> +       spin_unlock_irqrestore(&host->lock, flags);
> +}
> +
> +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;
> +
> +       spin_lock(&host->lock);
> +       irqs = readl(host->base + SDIO_IRQS);
> +       if (mrq && (irqs & REG_IRQS_CMD_INT)) {
> +               spin_unlock(&host->lock);
> +               return IRQ_WAKE_THREAD;
> +       }
> +
> +       spin_unlock(&host->lock);
> +       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);
> +       }
> +}
> +
> +struct mmc_command meson_mmc_cmd = {
> +       .opcode = MMC_STOP_TRANSMISSION,
> +       .flags  = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC,
> +};
> +
> +struct mmc_request meson_mmc_stop = {
> +       .cmd = &meson_mmc_cmd,
> +};
> +
> +static irqreturn_t meson_mmc_irq_thread(int irq, void *data)
> +{
> +       struct meson_mmc_host *host = (void *) data;
> +       unsigned long flags;
> +       struct mmc_request *mrq;
> +       u32 irqs, send;
> +
> +       spin_lock_irqsave(&host->lock, flags);
> +
> +       cancel_delayed_work_sync(&host->timeout_work);
> +       mrq = host->mrq;
> +
> +       if (!mrq) {
> +               spin_unlock_irqrestore(&host->lock, flags);
> +               return IRQ_HANDLED;
> +       }
> +
> +       if (host->state == STATE_STOP)
> +               goto out;
> +
> +       if (host->state != STATE_REQUEST) {
> +               dev_dbg(mmc_dev(host->mmc), "%s() rejected, state %u\n",
> +                       __func__, host->state);
> +               mrq->cmd->error = -EAGAIN;
> +               goto out;
> +       }
> +
> +       irqs = readl(host->base + SDIO_IRQS);
> +       send = readl(host->base + SDIO_SEND);
> +
> +       mrq->cmd->error = 0;
> +
> +       if (!mrq->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 {
> +                       mrq->data->bytes_xfered = mrq->data->blksz * mrq->data->blocks;
> +
> +                       if (mrq->data->flags & MMC_DATA_READ)
> +                               meson_mmc_map_dma(host, mrq->data, mrq->data->flags);
> +
> +                       if (mrq->stop) {
> +                               host->state = STATE_STOP;
> +                               meson_mmc_start_cmd(host->mmc, &meson_mmc_stop);
> +                               spin_unlock_irqrestore(&host->lock, flags);
> +                               return IRQ_HANDLED;
> +                       }
> +               }
> +       }
> +
> +out:
> +       host->state = STATE_IDLE;
> +       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;
> +
> +       spin_lock_irqsave(&host->lock, flags);
> +
> +       if (host->state == STATE_IDLE) {
> +               spin_unlock_irqrestore(&host->lock, flags);
> +               return;
> +       }
> +
> +       dev_err(mmc_dev(host->mmc), "Timeout on CMD%u\n", mrq->cmd->opcode);
> +
> +       host->state = STATE_TIMEOUT;
> +
> +       irqc = readl(host->base + SDIO_IRQC);
> +       irqc &= ~REG_IRQC_ARC_CMD_INT;
> +       writel(irqc, host->base + SDIO_IRQC);
> +
> +       mrq->cmd->error = -ETIMEDOUT;
> +
> +       host->state = STATE_IDLE;
> +       host->mrq = NULL;
> +       spin_unlock_irqrestore(&host->lock, flags);
> +
> +       mmc_request_done(host->mmc, mrq);
> +}
> +
> +static void meson_mmc_reset(struct mmc_host *mmc)
> +{
> +       struct meson_mmc_host *host = mmc_priv(mmc);
> +       u32 reg = 0;
> +
> +       dev_dbg(mmc_dev(mmc), "resetting mmc controller\n");
> +
> +       reg =  (2 << REG_CONF_WR_CRC_S);
> +       reg |= (2 << REG_CONF_WR_NWR_S);
> +       reg |= (3 << REG_CONF_M_END_S);
> +       reg |= (39 << REG_CONF_ARGU_BITS_S);
> +
> +       reg |= CLK_DIV;
> +       writel(reg, host->base + SDIO_CONF);
> +
> +       reg = readl(host->base + SDIO_IRQS);
> +       reg |= (REG_IRQS_CMD_INT);
> +       writel(reg, host->base + SDIO_IRQS);
> +}
> +
> +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 device_node *node = pdev->dev.of_node;
> +       int ret, sdio_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;
> +       spin_lock_init(&host->lock);
> +
> +       host->base = devm_ioremap_resource(&pdev->dev,
> +                               platform_get_resource(pdev, IORESOURCE_MEM, 0));
> +       if (IS_ERR(host->base)) {
> +               ret = PTR_ERR(host->base);
> +               goto error_free_host;
> +       }
> +
> +       host->irq = platform_get_irq(pdev, 0);
> +       ret = devm_request_threaded_irq(&pdev->dev, host->irq, meson_mmc_irq,
> +                       meson_mmc_irq_thread, 0, "meson_mmc", host);
> +       if (ret)
> +               goto error_free_host;
> +
> +       host->sg_cpu = dma_alloc_coherent(&pdev->dev, SDIO_BOUNCE_REQ_SIZE,
> +                                         &host->sg_dma, GFP_KERNEL);
> +       if (!host->sg_cpu) {
> +               dev_err(&pdev->dev, "Failed to allocate DMA descriptor mem\n");
> +               ret = -ENOMEM;
> +               goto error_free_host;
> +       }
> +
> +       host->clk_sdio = devm_clk_get(&pdev->dev, NULL);
> +       if (IS_ERR(host->clk_sdio)) {
> +               dev_err(&pdev->dev, "Could not get clk81 clock\n");
> +               ret = PTR_ERR(host->clk_sdio);
> +               goto error_free_dma;
> +       }
> +
> +       mmc->ops = &meson_mmc_ops;
> +
> +       mmc->max_segs = 1024;
> +       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->ocr_avail = MMC_VDD_33_34;
> +
> +       INIT_DELAYED_WORK(&host->timeout_work, meson_mmc_timeout);
> +       host->timeout = msecs_to_jiffies(2000);
> +       host->port = 0;
> +
> +       if (!of_property_read_u32(node, "meson,sdio-port", &sdio_port))
> +               host->port = sdio_port;
> +
> +       ret = mmc_of_parse(mmc);
> +       if (ret)
> +               goto error_free_dma;
> +
> +       platform_set_drvdata(pdev, mmc);
> +
> +       meson_mmc_reset(mmc);
> +       mmc_add_host(mmc);
> +
> +       dev_info(&pdev->dev, "base:0x%p irq:%u port:%u\n",
> +                host->base, host->irq, host->port);
> +
> +       return 0;
> +
> +error_free_dma:
> +       dma_free_coherent(&pdev->dev, SDIO_BOUNCE_REQ_SIZE,
> +                         host->sg_cpu, host->sg_dma);
> +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);
> +
> +       mmc_remove_host(mmc);
> +       disable_irq(host->irq);
> +
> +       dma_free_coherent(&pdev->dev, SDIO_BOUNCE_REQ_SIZE,
> +                         host->sg_cpu, host->sg_dma);
> +       mmc_free_host(mmc);
> +
> +       cancel_delayed_work_sync(&host->timeout_work);
> +
> +       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 = DRIVER_NAME,
> +               .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 <carlo@endlessm.com>");
> +MODULE_LICENSE("GPL");
> --
> 1.9.1
>
Carlo Caione June 10, 2015, 8:30 a.m. UTC | #3
On Tue, Jun 9, 2015 at 1:15 AM, Daniel Drake <drake@endlessm.com> wrote:
> Hi Carlo,

Hey Daniel,

> On Mon, Jun 8, 2015 at 11:09 AM, Carlo Caione <carlo@caione.org> wrote:
>> From: Carlo Caione <carlo@endlessm.com>
>>
>> Add a driver for the SD/MMC host found on the Amlogic MesonX SoCs. It
>> 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. It also supports
>> SDSC, SDHC and SDXC memory card default speed.
>
> Nice work, thanks for this!
>
>> This patch adds also the bindinding documentation.
>
> "binding" typo

ops

>> +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;
>> +
>> +       spin_lock_irqsave(&host->lock, flags);
>> +
>> +       if (host->state != STATE_IDLE) {
>> +               dev_dbg(mmc_dev(mmc), "%s() rejected, state %u\n",
>> +                       __func__, host->state);
>> +               mrq->cmd->error = -EAGAIN;
>> +               spin_unlock_irqrestore(&host->lock, flags);
>> +               mmc_request_done(mmc, mrq);
>> +               return;
>> +       }
>> +
>> +       if (host->ferror) {
>> +               cmd->error = host->ferror;
>> +               spin_unlock_irqrestore(&host->lock, flags);
>> +               mmc_request_done(mmc, mrq);
>> +               return;
>> +       }
>> +
>> +       dev_dbg(mmc_dev(mmc), "CMD%d(%08x) arg %x len %d flags %08x\n",
>> +               cmd->opcode & 0x3f, cmd->opcode, cmd->arg,
>> +               mrq->data ? mrq->data->blksz * mrq->data->blocks : 0,
>> +               mrq->cmd->flags);
>> +
>> +       /* Filter out CMD 5/52/53 */
>> +       if (cmd->opcode == SD_IO_SEND_OP_COND ||
>> +           cmd->opcode == SD_IO_RW_DIRECT ||
>> +           cmd->opcode == SD_IO_RW_EXTENDED) {
>> +               dev_dbg(mmc_dev(host->mmc), "CMD%d not supported\n", cmd->opcode);
>> +               cmd->error = -EINVAL;
>> +               spin_unlock_irqrestore(&host->lock, flags);
>> +               mmc_request_done(mmc, mrq);
>> +               return;
>> +       }
>> +
>> +       host->state = STATE_REQUEST;
>> +
>> +       if (data) {
>> +               meson_mmc_map_dma(host, data, data->flags);
>> +               writel(host->sg_dma, host->base + SDIO_ADDR);
>
> As the sg_dma buffer is constant, can you do this register write just
> once at probe time? I wonder if we really need to do it on each
> request.

Unfortunately it doesn't work. It is not documented but I guess that
writing to that register triggers the DMA transfer and the register is
zeroed afterwards.

> Or, going deeper, I wonder if we can avoid this bounce buffer
> entirely. Can you try setting mmc->max_segs to 1 then passing the
> single data segment directly to the hardware with
>    writel(sg_dma_address(data->sg), host->base + SDIO_ADDR)
>
> If that turns out to be more complicated then it sounds, then feel
> free to push it off to a later driver revision.

Yup. I'll fix it in v2 putting in CC linux-mmc.

Thanks,
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/mmc/meson-mmc.txt b/Documentation/devicetree/bindings/mmc/meson-mmc.txt
new file mode 100644
index 0000000..839569a
--- /dev/null
+++ b/Documentation/devicetree/bindings/mmc/meson-mmc.txt
@@ -0,0 +1,38 @@ 
+* Amlogic MesonX MMC controller
+
+The highspeed MMC host controller on Amlogic SoCs provides an interface
+for MMC, SD, SDIO and SDHC types of memory cards.
+
+Supported maximum speeds are the ones of the eMMC standard 4.41 as well
+as the speed of SD standard 2.0.
+
+Required properties:
+ - compatible : "amlogic,meson-mmc"
+ - reg : mmc controller base registers
+ - interrupts : mmc controller interrupt
+ - clocks : phandle to SDIO clock provider
+
+Optional properties:
+ - meson,sdio-port : 0 for SDIO port A, 1 for SDIO port B. (default: 0)
+ - for cd, bus-width and additional generic mmc parameters
+   please refer to mmc.txt within this directory
+
+Examples:
+	- Within .dtsi:
+	mmc0: mmc@c1108c20 {
+		compatible = "amlogic,meson-mmc";
+		reg = <0xc1108c20 0x20>;
+		interrupts = <0 28 1>;
+		clocks = <&clkc CLKID_CLK81>;
+		status = "disabled";
+	};
+
+	- Within .dts:
+	&mmc0 {
+		status = "okay";
+		pinctrl-0 = <&mmc0_sd_b_pins>;
+		pinctrl-names = "default";
+		meson,sdio-port = <1>;
+		cd-gpios = <&gpio CARD_6 0>;
+		cd-inverted;
+	};
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index b1f837e..e61b8d6 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -764,6 +764,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 MesonX SD/MMC Host Controller support"
+	depends on ARCH_MESON
+	help
+	  This selects support for the SD/MMC Host Controller on
+	  Amlogic MesonX 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 e3ab5b9..c0455e7 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -53,6 +53,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..0bcae9d
--- /dev/null
+++ b/drivers/mmc/host/meson-mmc.c
@@ -0,0 +1,615 @@ 
+/*
+ * mesonsd.c - Meson SDH Controller
+ *
+ * Copyright (C) 2015 Endless Mobile, Inc.
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#define DRIVER_NAME	"meson-mmc"
+
+#define DEBUG
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/slot-gpio.h>
+
+#include <linux/of_address.h>
+#include <linux/of_gpio.h>
+#include <linux/of_platform.h>
+
+#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_IRQC_SOFT_RESET	BIT(15)
+
+#define REG_CONF_WR_CRC_S	(29)
+#define REG_CONF_WR_NWR_S	(23)
+#define REG_CONF_M_END_S	(21)
+#define REG_CONF_ARGU_BITS_S	(12)
+#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 CLK_DIV			(0x1f4)
+
+#define SDIO_BOUNCE_REQ_SIZE	(128 * 1024)
+
+enum mmcif_state {
+	STATE_IDLE,
+	STATE_REQUEST,
+	STATE_IOS,
+	STATE_TIMEOUT,
+	STATE_STOP,
+};
+
+struct meson_mmc_host {
+	struct mmc_host		*mmc;
+	struct mmc_request	*mrq;
+	struct delayed_work     timeout_work;
+	struct clk		*clk_sdio;
+	spinlock_t		lock;
+	void __iomem		*base;
+	void			*sg_cpu;
+	dma_addr_t		sg_dma;
+	long			timeout;
+	int			irq;
+	int			ferror;
+	unsigned int		bus_width;
+	unsigned int		port;
+	enum mmcif_state	state;
+};
+
+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_sdio) / 2;
+	if (clk_rate < 0) {
+		dev_err(mmc_dev(mmc), "cannot get clock rate\n");
+		return -EINVAL;
+	}
+
+	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);
+
+	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;
+
+	if (host->state != STATE_IDLE) {
+		dev_dbg(mmc_dev(mmc), "%s() rejected, state %u\n",
+			__func__, host->state);
+		return;
+	}
+
+	host->state = STATE_IOS;
+
+	reg = readl(host->base + SDIO_CONF);
+
+	switch (ios->bus_width) {
+	case MMC_BUS_WIDTH_1:
+		host->bus_width = 0;
+		reg &= ~REG_CONF_BUS_WIDTH;
+		dev_dbg(mmc_dev(mmc), "bus width: 1bit\n");
+		break;
+	case MMC_BUS_WIDTH_4:
+		host->bus_width = 1;
+		reg |= REG_CONF_BUS_WIDTH;
+		dev_dbg(mmc_dev(mmc), "bus width: 4bit\n");
+		break;
+	case MMC_BUS_WIDTH_8:
+	default:
+		dev_err(mmc_dev(mmc), "SDIO controller doesn't support 8bit data bus\n");
+		host->ferror = -EINVAL;
+		return;
+	}
+
+	writel(reg, host->base + SDIO_CONF);
+
+	if (ios->clock && ios->power_mode)
+		host->ferror = meson_mmc_clk_set_rate(mmc, ios);
+
+	host->state = STATE_IDLE;
+}
+
+static void meson_mmc_soft_reset(struct meson_mmc_host *host)
+{
+	u32 irqc;
+
+	irqc = readl(host->base + SDIO_IRQC);
+	irqc |= REG_IRQC_SOFT_RESET;
+	writel(irqc, host->base + SDIO_IRQC);
+	udelay(2);
+}
+
+static void meson_mmc_start_cmd(struct mmc_host *mmc,
+				struct mmc_request *mrq)
+{
+	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(mrq->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 (!(mrq->cmd->flags & MMC_RSP_CRC))
+		send |= REG_SEND_RESP_NO_CRC7;
+
+	if (mrq->cmd->flags & MMC_RSP_BUSY)
+		send |= REG_SEND_CHECK_BUSY_D0;
+
+	if (mrq->data) {
+		send &= ~(REG_SEND_REP_PACK_N_M << REG_SEND_REP_PACK_N_S);
+		send |= ((mrq->data->blocks - 1) << REG_SEND_REP_PACK_N_S);
+
+		ext &= ~(REG_EXT_DAT_RW_NUM_M << REG_EXT_DAT_RW_NUM_S);
+		if (host->bus_width)
+			pack_size = mrq->data->blksz * 8 + (16 - 1) * 4;
+		else
+			pack_size = mrq->data->blksz * 8 + (16 - 1);
+		ext |= (pack_size << REG_EXT_DAT_RW_NUM_S);
+
+		if (mrq->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 | mrq->cmd->opcode);
+
+	meson_mmc_soft_reset(host);
+
+	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(mrq->cmd->arg, host->base + SDIO_ARGU);
+	writel(ext, host->base + SDIO_EXT);
+	writel(send, host->base + SDIO_SEND);
+}
+
+static void meson_mmc_map_dma(struct meson_mmc_host *host,
+			      struct mmc_data *data,
+			      unsigned int flags)
+{
+	size_t buflen = data->blksz * data->blocks;
+
+	if (flags & MMC_DATA_READ)
+		sg_copy_from_buffer(data->sg, data->sg_len, host->sg_cpu, buflen);
+	else
+		sg_copy_to_buffer(data->sg, data->sg_len, host->sg_cpu, buflen);
+}
+
+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;
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	if (host->state != STATE_IDLE) {
+		dev_dbg(mmc_dev(mmc), "%s() rejected, state %u\n",
+			__func__, host->state);
+		mrq->cmd->error = -EAGAIN;
+		spin_unlock_irqrestore(&host->lock, flags);
+		mmc_request_done(mmc, mrq);
+		return;
+	}
+
+	if (host->ferror) {
+		cmd->error = host->ferror;
+		spin_unlock_irqrestore(&host->lock, flags);
+		mmc_request_done(mmc, mrq);
+		return;
+	}
+
+	dev_dbg(mmc_dev(mmc), "CMD%d(%08x) arg %x len %d flags %08x\n",
+		cmd->opcode & 0x3f, cmd->opcode, cmd->arg,
+		mrq->data ? mrq->data->blksz * mrq->data->blocks : 0,
+		mrq->cmd->flags);
+
+	/* Filter out CMD 5/52/53 */
+	if (cmd->opcode == SD_IO_SEND_OP_COND ||
+	    cmd->opcode == SD_IO_RW_DIRECT ||
+	    cmd->opcode == SD_IO_RW_EXTENDED) {
+		dev_dbg(mmc_dev(host->mmc), "CMD%d not supported\n", cmd->opcode);
+		cmd->error = -EINVAL;
+		spin_unlock_irqrestore(&host->lock, flags);
+		mmc_request_done(mmc, mrq);
+		return;
+	}
+
+	host->state = STATE_REQUEST;
+
+	if (data) {
+		meson_mmc_map_dma(host, data, data->flags);
+		writel(host->sg_dma, host->base + SDIO_ADDR);
+	}
+
+	host->mrq = mrq;
+	schedule_delayed_work(&host->timeout_work, host->timeout);
+	meson_mmc_start_cmd(mmc, mrq);
+
+	spin_unlock_irqrestore(&host->lock, flags);
+}
+
+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;
+
+	spin_lock(&host->lock);
+	irqs = readl(host->base + SDIO_IRQS);
+	if (mrq && (irqs & REG_IRQS_CMD_INT)) {
+		spin_unlock(&host->lock);
+		return IRQ_WAKE_THREAD;
+	}
+
+	spin_unlock(&host->lock);
+	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);
+	}
+}
+
+struct mmc_command meson_mmc_cmd = {
+	.opcode	= MMC_STOP_TRANSMISSION,
+	.flags	= MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC,
+};
+
+struct mmc_request meson_mmc_stop = {
+	.cmd = &meson_mmc_cmd,
+};
+
+static irqreturn_t meson_mmc_irq_thread(int irq, void *data)
+{
+	struct meson_mmc_host *host = (void *) data;
+	unsigned long flags;
+	struct mmc_request *mrq;
+	u32 irqs, send;
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	cancel_delayed_work_sync(&host->timeout_work);
+	mrq = host->mrq;
+
+	if (!mrq) {
+		spin_unlock_irqrestore(&host->lock, flags);
+		return IRQ_HANDLED;
+	}
+
+	if (host->state == STATE_STOP)
+		goto out;
+
+	if (host->state != STATE_REQUEST) {
+		dev_dbg(mmc_dev(host->mmc), "%s() rejected, state %u\n",
+			__func__, host->state);
+		mrq->cmd->error = -EAGAIN;
+		goto out;
+	}
+
+	irqs = readl(host->base + SDIO_IRQS);
+	send = readl(host->base + SDIO_SEND);
+
+	mrq->cmd->error = 0;
+
+	if (!mrq->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 {
+			mrq->data->bytes_xfered = mrq->data->blksz * mrq->data->blocks;
+
+			if (mrq->data->flags & MMC_DATA_READ)
+				meson_mmc_map_dma(host, mrq->data, mrq->data->flags);
+
+			if (mrq->stop) {
+				host->state = STATE_STOP;
+				meson_mmc_start_cmd(host->mmc, &meson_mmc_stop);
+				spin_unlock_irqrestore(&host->lock, flags);
+				return IRQ_HANDLED;
+			}
+		}
+	}
+
+out:
+	host->state = STATE_IDLE;
+	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;
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	if (host->state == STATE_IDLE) {
+		spin_unlock_irqrestore(&host->lock, flags);
+		return;
+	}
+
+	dev_err(mmc_dev(host->mmc), "Timeout on CMD%u\n", mrq->cmd->opcode);
+
+	host->state = STATE_TIMEOUT;
+
+	irqc = readl(host->base + SDIO_IRQC);
+	irqc &= ~REG_IRQC_ARC_CMD_INT;
+	writel(irqc, host->base + SDIO_IRQC);
+
+	mrq->cmd->error = -ETIMEDOUT;
+
+	host->state = STATE_IDLE;
+	host->mrq = NULL;
+	spin_unlock_irqrestore(&host->lock, flags);
+
+	mmc_request_done(host->mmc, mrq);
+}
+
+static void meson_mmc_reset(struct mmc_host *mmc)
+{
+	struct meson_mmc_host *host = mmc_priv(mmc);
+	u32 reg = 0;
+
+	dev_dbg(mmc_dev(mmc), "resetting mmc controller\n");
+
+	reg =  (2 << REG_CONF_WR_CRC_S);
+	reg |= (2 << REG_CONF_WR_NWR_S);
+	reg |= (3 << REG_CONF_M_END_S);
+	reg |= (39 << REG_CONF_ARGU_BITS_S);
+
+	reg |= CLK_DIV;
+	writel(reg, host->base + SDIO_CONF);
+
+	reg = readl(host->base + SDIO_IRQS);
+	reg |= (REG_IRQS_CMD_INT);
+	writel(reg, host->base + SDIO_IRQS);
+}
+
+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 device_node *node = pdev->dev.of_node;
+	int ret, sdio_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;
+	spin_lock_init(&host->lock);
+
+	host->base = devm_ioremap_resource(&pdev->dev,
+				platform_get_resource(pdev, IORESOURCE_MEM, 0));
+	if (IS_ERR(host->base)) {
+		ret = PTR_ERR(host->base);
+		goto error_free_host;
+	}
+
+	host->irq = platform_get_irq(pdev, 0);
+	ret = devm_request_threaded_irq(&pdev->dev, host->irq, meson_mmc_irq,
+			meson_mmc_irq_thread, 0, "meson_mmc", host);
+	if (ret)
+		goto error_free_host;
+
+	host->sg_cpu = dma_alloc_coherent(&pdev->dev, SDIO_BOUNCE_REQ_SIZE,
+					  &host->sg_dma, GFP_KERNEL);
+	if (!host->sg_cpu) {
+		dev_err(&pdev->dev, "Failed to allocate DMA descriptor mem\n");
+		ret = -ENOMEM;
+		goto error_free_host;
+	}
+
+	host->clk_sdio = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(host->clk_sdio)) {
+		dev_err(&pdev->dev, "Could not get clk81 clock\n");
+		ret = PTR_ERR(host->clk_sdio);
+		goto error_free_dma;
+	}
+
+	mmc->ops = &meson_mmc_ops;
+
+	mmc->max_segs = 1024;
+	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->ocr_avail = MMC_VDD_33_34;
+
+	INIT_DELAYED_WORK(&host->timeout_work, meson_mmc_timeout);
+	host->timeout = msecs_to_jiffies(2000);
+	host->port = 0;
+
+	if (!of_property_read_u32(node, "meson,sdio-port", &sdio_port))
+		host->port = sdio_port;
+
+	ret = mmc_of_parse(mmc);
+	if (ret)
+		goto error_free_dma;
+
+	platform_set_drvdata(pdev, mmc);
+
+	meson_mmc_reset(mmc);
+	mmc_add_host(mmc);
+
+	dev_info(&pdev->dev, "base:0x%p irq:%u port:%u\n",
+		 host->base, host->irq, host->port);
+
+	return 0;
+
+error_free_dma:
+	dma_free_coherent(&pdev->dev, SDIO_BOUNCE_REQ_SIZE,
+			  host->sg_cpu, host->sg_dma);
+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);
+
+	mmc_remove_host(mmc);
+	disable_irq(host->irq);
+
+	dma_free_coherent(&pdev->dev, SDIO_BOUNCE_REQ_SIZE,
+			  host->sg_cpu, host->sg_dma);
+	mmc_free_host(mmc);
+
+	cancel_delayed_work_sync(&host->timeout_work);
+
+	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 = DRIVER_NAME,
+		.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 <carlo@endlessm.com>");
+MODULE_LICENSE("GPL");