Message ID | 20171003112417.31860-3-martin.blumenstingl@googlemail.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On 3 October 2017 at 13:24, Martin Blumenstingl <martin.blumenstingl@googlemail.com> wrote: > From: Carlo Caione <carlo@endlessm.com> > > Add a driver for the SDIO/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. > > The controller provides an internal "mux" which allows connecting up to > three MMC devices to it. Only one device can be used at a time though > since the registers are shared across all devices. The driver takes care > of synchronizing access (similar to the dw_mmc driver). > The maximum supported bus-width is 4-bits. > > Amlogic's GPL kernel sources call the corresponding driver "aml_sdio" to > differentiate it from the other MMC controller in (at least the Meson8 > and Meson8b) the SoCs (they call the other drivers aml_sdhc and > aml_sdhc_m8, which seem to support a bus-width of up to 8-bits). This > means that there are three different MMC host controller IP blocks from > Amlogic (each of them with completely own register layout and features): > - "SDIO": 1 and 4 bit bus width, support for high-speed modes up to > UHS-I SDR50, part of Meson6, Meson8 and Meson8b (the driver from this > patch targets this controller) > - "SDHC": 1, 4 and 8 bit bus width, compatible with standard iNAND > interface, support for speeds up to HS200 and MMC spec up to version > 4.5x, part of Meson8 and Meson8b SoCs (there is no mainline driver > for this controller yet) > - "SDEMMC": 1, 4 and 8 bit bus width, support for speeds up to HS400 > and MMC spec up to version 5.0, part of the Meson GX (64-bit) SoCs > (supported by the meson-gx MMC host driver) > > Signed-off-by: Carlo Caione <carlo@endlessm.com> > Signed-off-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com> Thanks, applied for next! Kind regards Uffe > --- > drivers/mmc/host/Kconfig | 13 + > drivers/mmc/host/Makefile | 1 + > drivers/mmc/host/meson-mx-sdio.c | 769 +++++++++++++++++++++++++++++++++++++++ > 3 files changed, 783 insertions(+) > create mode 100644 drivers/mmc/host/meson-mx-sdio.c > > diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig > index 8c15637178ff..a27b99c77661 100644 > --- a/drivers/mmc/host/Kconfig > +++ b/drivers/mmc/host/Kconfig > @@ -352,6 +352,19 @@ config MMC_MESON_GX > > If you have a controller with this interface, say Y here. > > +config MMC_MESON_MX_SDIO > + tristate "Amlogic Meson6/Meson8/Meson8b SD/MMC Host Controller support" > + depends on ARCH_MESON || COMPILE_TEST > + depends on COMMON_CLK > + depends on HAS_DMA > + depends on OF > + help > + This selects support for the SD/MMC Host Controller on > + Amlogic Meson6, Meson8 and Meson8b SoCs. > + > + If you have a controller with this interface, say Y or M here. > + If unsure, say N. > + > config MMC_MOXART > tristate "MOXART SD/MMC Host Controller support" > depends on ARCH_MOXART && MMC > diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile > index 303f5cd46cd9..0de72a531240 100644 > --- a/drivers/mmc/host/Makefile > +++ b/drivers/mmc/host/Makefile > @@ -64,6 +64,7 @@ obj-$(CONFIG_MMC_VUB300) += vub300.o > obj-$(CONFIG_MMC_USHC) += ushc.o > obj-$(CONFIG_MMC_WMT) += wmt-sdmmc.o > obj-$(CONFIG_MMC_MESON_GX) += meson-gx-mmc.o > +obj-$(CONFIG_MMC_MESON_MX_SDIO) += meson-mx-sdio.o > obj-$(CONFIG_MMC_MOXART) += moxart-mmc.o > obj-$(CONFIG_MMC_SUNXI) += sunxi-mmc.o > obj-$(CONFIG_MMC_USDHI6ROL0) += usdhi6rol0.o > diff --git a/drivers/mmc/host/meson-mx-sdio.c b/drivers/mmc/host/meson-mx-sdio.c > new file mode 100644 > index 000000000000..19b499bbe691 > --- /dev/null > +++ b/drivers/mmc/host/meson-mx-sdio.c > @@ -0,0 +1,769 @@ > +/* > + * meson-mx-sdio.c - Meson6, Meson8 and Meson8b SDIO/MMC Host Controller > + * > + * Copyright (C) 2015 Endless Mobile, Inc. > + * Author: Carlo Caione <carlo@endlessm.com> > + * Copyright (C) 2017 Martin Blumenstingl <martin.blumenstingl@googlemail.com> > + * > + * 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 <linux/bitfield.h> > +#include <linux/clk.h> > +#include <linux/clk-provider.h> > +#include <linux/delay.h> > +#include <linux/device.h> > +#include <linux/dma-mapping.h> > +#include <linux/module.h> > +#include <linux/interrupt.h> > +#include <linux/ioport.h> > +#include <linux/platform_device.h> > +#include <linux/of_platform.h> > +#include <linux/timer.h> > +#include <linux/types.h> > + > +#include <linux/mmc/host.h> > +#include <linux/mmc/mmc.h> > +#include <linux/mmc/sdio.h> > +#include <linux/mmc/slot-gpio.h> > + > +#define MESON_MX_SDIO_ARGU 0x00 > + > +#define MESON_MX_SDIO_SEND 0x04 > + #define MESON_MX_SDIO_SEND_COMMAND_INDEX_MASK GENMASK(7, 0) > + #define MESON_MX_SDIO_SEND_CMD_RESP_BITS_MASK GENMASK(15, 8) > + #define MESON_MX_SDIO_SEND_RESP_WITHOUT_CRC7 BIT(16) > + #define MESON_MX_SDIO_SEND_RESP_HAS_DATA BIT(17) > + #define MESON_MX_SDIO_SEND_RESP_CRC7_FROM_8 BIT(18) > + #define MESON_MX_SDIO_SEND_CHECK_DAT0_BUSY BIT(19) > + #define MESON_MX_SDIO_SEND_DATA BIT(20) > + #define MESON_MX_SDIO_SEND_USE_INT_WINDOW BIT(21) > + #define MESON_MX_SDIO_SEND_REPEAT_PACKAGE_TIMES_MASK GENMASK(31, 24) > + > +#define MESON_MX_SDIO_CONF 0x08 > + #define MESON_MX_SDIO_CONF_CMD_CLK_DIV_SHIFT 0 > + #define MESON_MX_SDIO_CONF_CMD_CLK_DIV_WIDTH 10 > + #define MESON_MX_SDIO_CONF_CMD_DISABLE_CRC BIT(10) > + #define MESON_MX_SDIO_CONF_CMD_OUT_AT_POSITIVE_EDGE BIT(11) > + #define MESON_MX_SDIO_CONF_CMD_ARGUMENT_BITS_MASK GENMASK(17, 12) > + #define MESON_MX_SDIO_CONF_RESP_LATCH_AT_NEGATIVE_EDGE BIT(18) > + #define MESON_MX_SDIO_CONF_DATA_LATCH_AT_NEGATIVE_EDGE BIT(19) > + #define MESON_MX_SDIO_CONF_BUS_WIDTH BIT(20) > + #define MESON_MX_SDIO_CONF_M_ENDIAN_MASK GENMASK(22, 21) > + #define MESON_MX_SDIO_CONF_WRITE_NWR_MASK GENMASK(28, 23) > + #define MESON_MX_SDIO_CONF_WRITE_CRC_OK_STATUS_MASK GENMASK(31, 29) > + > +#define MESON_MX_SDIO_IRQS 0x0c > + #define MESON_MX_SDIO_IRQS_STATUS_STATE_MACHINE_MASK GENMASK(3, 0) > + #define MESON_MX_SDIO_IRQS_CMD_BUSY BIT(4) > + #define MESON_MX_SDIO_IRQS_RESP_CRC7_OK BIT(5) > + #define MESON_MX_SDIO_IRQS_DATA_READ_CRC16_OK BIT(6) > + #define MESON_MX_SDIO_IRQS_DATA_WRITE_CRC16_OK BIT(7) > + #define MESON_MX_SDIO_IRQS_IF_INT BIT(8) > + #define MESON_MX_SDIO_IRQS_CMD_INT BIT(9) > + #define MESON_MX_SDIO_IRQS_STATUS_INFO_MASK GENMASK(15, 12) > + #define MESON_MX_SDIO_IRQS_TIMING_OUT_INT BIT(16) > + #define MESON_MX_SDIO_IRQS_AMRISC_TIMING_OUT_INT_EN BIT(17) > + #define MESON_MX_SDIO_IRQS_ARC_TIMING_OUT_INT_EN BIT(18) > + #define MESON_MX_SDIO_IRQS_TIMING_OUT_COUNT_MASK GENMASK(31, 19) > + > +#define MESON_MX_SDIO_IRQC 0x10 > + #define MESON_MX_SDIO_IRQC_ARC_IF_INT_EN BIT(3) > + #define MESON_MX_SDIO_IRQC_ARC_CMD_INT_EN BIT(4) > + #define MESON_MX_SDIO_IRQC_IF_CONFIG_MASK GENMASK(7, 6) > + #define MESON_MX_SDIO_IRQC_FORCE_DATA_CLK BIT(8) > + #define MESON_MX_SDIO_IRQC_FORCE_DATA_CMD BIT(9) > + #define MESON_MX_SDIO_IRQC_FORCE_DATA_DAT_MASK GENMASK(10, 13) > + #define MESON_MX_SDIO_IRQC_SOFT_RESET BIT(15) > + #define MESON_MX_SDIO_IRQC_FORCE_HALT BIT(30) > + #define MESON_MX_SDIO_IRQC_HALT_HOLE BIT(31) > + > +#define MESON_MX_SDIO_MULT 0x14 > + #define MESON_MX_SDIO_MULT_PORT_SEL_MASK GENMASK(1, 0) > + #define MESON_MX_SDIO_MULT_MEMORY_STICK_ENABLE BIT(2) > + #define MESON_MX_SDIO_MULT_MEMORY_STICK_SCLK_ALWAYS BIT(3) > + #define MESON_MX_SDIO_MULT_STREAM_ENABLE BIT(4) > + #define MESON_MX_SDIO_MULT_STREAM_8BITS_MODE BIT(5) > + #define MESON_MX_SDIO_MULT_WR_RD_OUT_INDEX BIT(8) > + #define MESON_MX_SDIO_MULT_DAT0_DAT1_SWAPPED BIT(10) > + #define MESON_MX_SDIO_MULT_DAT1_DAT0_SWAPPED BIT(11) > + #define MESON_MX_SDIO_MULT_RESP_READ_INDEX_MASK GENMASK(15, 12) > + > +#define MESON_MX_SDIO_ADDR 0x18 > + > +#define MESON_MX_SDIO_EXT 0x1c > + #define MESON_MX_SDIO_EXT_DATA_RW_NUMBER_MASK GENMASK(29, 16) > + > +#define MESON_MX_SDIO_BOUNCE_REQ_SIZE (128 * 1024) > +#define MESON_MX_SDIO_RESPONSE_CRC16_BITS (16 - 1) > +#define MESON_MX_SDIO_MAX_SLOTS 3 > + > +struct meson_mx_mmc_host { > + struct device *controller_dev; > + > + struct clk *parent_clk; > + struct clk *core_clk; > + struct clk_divider cfg_div; > + struct clk *cfg_div_clk; > + struct clk_fixed_factor fixed_factor; > + struct clk *fixed_factor_clk; > + > + void __iomem *base; > + int irq; > + spinlock_t irq_lock; > + > + struct timer_list cmd_timeout; > + > + unsigned int slot_id; > + struct mmc_host *mmc; > + > + struct mmc_request *mrq; > + struct mmc_command *cmd; > + int error; > +}; > + > +static void meson_mx_mmc_mask_bits(struct mmc_host *mmc, char reg, u32 mask, > + u32 val) > +{ > + struct meson_mx_mmc_host *host = mmc_priv(mmc); > + u32 regval; > + > + regval = readl(host->base + reg); > + regval &= ~mask; > + regval |= (val & mask); > + > + writel(regval, host->base + reg); > +} > + > +static void meson_mx_mmc_soft_reset(struct meson_mx_mmc_host *host) > +{ > + writel(MESON_MX_SDIO_IRQC_SOFT_RESET, host->base + MESON_MX_SDIO_IRQC); > + udelay(2); > +} > + > +static struct mmc_command *meson_mx_mmc_get_next_cmd(struct mmc_command *cmd) > +{ > + if (cmd->opcode == MMC_SET_BLOCK_COUNT && !cmd->error) > + return cmd->mrq->cmd; > + else if (mmc_op_multi(cmd->opcode) && > + (!cmd->mrq->sbc || cmd->error || cmd->data->error)) > + return cmd->mrq->stop; > + else > + return NULL; > +} > + > +static void meson_mx_mmc_start_cmd(struct mmc_host *mmc, > + struct mmc_command *cmd) > +{ > + struct meson_mx_mmc_host *host = mmc_priv(mmc); > + unsigned int pack_size; > + unsigned long irqflags, timeout; > + u32 mult, send = 0, ext = 0; > + > + host->cmd = cmd; > + > + if (cmd->busy_timeout) > + timeout = msecs_to_jiffies(cmd->busy_timeout); > + else > + timeout = msecs_to_jiffies(1000); > + > + switch (mmc_resp_type(cmd)) { > + case MMC_RSP_R1: > + case MMC_RSP_R1B: > + case MMC_RSP_R3: > + /* 7 (CMD) + 32 (response) + 7 (CRC) -1 */ > + send |= FIELD_PREP(MESON_MX_SDIO_SEND_CMD_RESP_BITS_MASK, 45); > + break; > + case MMC_RSP_R2: > + /* 7 (CMD) + 120 (response) + 7 (CRC) -1 */ > + send |= FIELD_PREP(MESON_MX_SDIO_SEND_CMD_RESP_BITS_MASK, 133); > + send |= MESON_MX_SDIO_SEND_RESP_CRC7_FROM_8; > + break; > + default: > + break; > + } > + > + if (!(cmd->flags & MMC_RSP_CRC)) > + send |= MESON_MX_SDIO_SEND_RESP_WITHOUT_CRC7; > + > + if (cmd->flags & MMC_RSP_BUSY) > + send |= MESON_MX_SDIO_SEND_CHECK_DAT0_BUSY; > + > + if (cmd->data) { > + send |= FIELD_PREP(MESON_MX_SDIO_SEND_REPEAT_PACKAGE_TIMES_MASK, > + (cmd->data->blocks - 1)); > + > + pack_size = cmd->data->blksz * BITS_PER_BYTE; > + if (mmc->ios.bus_width == MMC_BUS_WIDTH_4) > + pack_size += MESON_MX_SDIO_RESPONSE_CRC16_BITS * 4; > + else > + pack_size += MESON_MX_SDIO_RESPONSE_CRC16_BITS * 1; > + > + ext |= FIELD_PREP(MESON_MX_SDIO_EXT_DATA_RW_NUMBER_MASK, > + pack_size); > + > + if (cmd->data->flags & MMC_DATA_WRITE) > + send |= MESON_MX_SDIO_SEND_DATA; > + else > + send |= MESON_MX_SDIO_SEND_RESP_HAS_DATA; > + > + cmd->data->bytes_xfered = 0; > + } > + > + send |= FIELD_PREP(MESON_MX_SDIO_SEND_COMMAND_INDEX_MASK, > + (0x40 | cmd->opcode)); > + > + spin_lock_irqsave(&host->irq_lock, irqflags); > + > + mult = readl(host->base + MESON_MX_SDIO_MULT); > + mult &= ~MESON_MX_SDIO_MULT_PORT_SEL_MASK; > + mult |= FIELD_PREP(MESON_MX_SDIO_MULT_PORT_SEL_MASK, host->slot_id); > + mult |= BIT(31); > + writel(mult, host->base + MESON_MX_SDIO_MULT); > + > + /* enable the CMD done interrupt */ > + meson_mx_mmc_mask_bits(mmc, MESON_MX_SDIO_IRQC, > + MESON_MX_SDIO_IRQC_ARC_CMD_INT_EN, > + MESON_MX_SDIO_IRQC_ARC_CMD_INT_EN); > + > + /* clear pending interrupts */ > + meson_mx_mmc_mask_bits(mmc, MESON_MX_SDIO_IRQS, > + MESON_MX_SDIO_IRQS_CMD_INT, > + MESON_MX_SDIO_IRQS_CMD_INT); > + > + writel(cmd->arg, host->base + MESON_MX_SDIO_ARGU); > + writel(ext, host->base + MESON_MX_SDIO_EXT); > + writel(send, host->base + MESON_MX_SDIO_SEND); > + > + spin_unlock_irqrestore(&host->irq_lock, irqflags); > + > + mod_timer(&host->cmd_timeout, jiffies + timeout); > +} > + > +static void meson_mx_mmc_request_done(struct meson_mx_mmc_host *host) > +{ > + struct mmc_request *mrq; > + > + mrq = host->mrq; > + > + host->mrq = NULL; > + host->cmd = NULL; > + > + mmc_request_done(host->mmc, mrq); > +} > + > +static void meson_mx_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) > +{ > + struct meson_mx_mmc_host *host = mmc_priv(mmc); > + unsigned short vdd = ios->vdd; > + unsigned long clk_rate = ios->clock; > + > + switch (ios->bus_width) { > + case MMC_BUS_WIDTH_1: > + meson_mx_mmc_mask_bits(mmc, MESON_MX_SDIO_CONF, > + MESON_MX_SDIO_CONF_BUS_WIDTH, 0); > + break; > + > + case MMC_BUS_WIDTH_4: > + meson_mx_mmc_mask_bits(mmc, MESON_MX_SDIO_CONF, > + MESON_MX_SDIO_CONF_BUS_WIDTH, > + MESON_MX_SDIO_CONF_BUS_WIDTH); > + break; > + > + case MMC_BUS_WIDTH_8: > + default: > + dev_err(mmc_dev(mmc), "unsupported bus width: %d\n", > + ios->bus_width); > + host->error = -EINVAL; > + return; > + } > + > + host->error = clk_set_rate(host->cfg_div_clk, ios->clock); > + if (host->error) { > + dev_warn(mmc_dev(mmc), > + "failed to set MMC clock to %lu: %d\n", > + clk_rate, host->error); > + return; > + } > + > + mmc->actual_clock = clk_get_rate(host->cfg_div_clk); > + > + switch (ios->power_mode) { > + case MMC_POWER_OFF: > + vdd = 0; > + /* fall-through: */ > + case MMC_POWER_UP: > + if (!IS_ERR(mmc->supply.vmmc)) { > + host->error = mmc_regulator_set_ocr(mmc, > + mmc->supply.vmmc, > + vdd); > + if (host->error) > + return; > + } > + break; > + } > +} > + > +static int meson_mx_mmc_map_dma(struct mmc_host *mmc, struct mmc_request *mrq) > +{ > + struct mmc_data *data = mrq->data; > + int dma_len; > + struct scatterlist *sg; > + > + if (!data) > + return 0; > + > + sg = data->sg; > + if (sg->offset & 3 || sg->length & 3) { > + dev_err(mmc_dev(mmc), > + "unaligned scatterlist: offset %x length %d\n", > + sg->offset, sg->length); > + return -EINVAL; > + } > + > + dma_len = dma_map_sg(mmc_dev(mmc), data->sg, data->sg_len, > + mmc_get_dma_dir(data)); > + if (dma_len <= 0) { > + dev_err(mmc_dev(mmc), "dma_map_sg failed\n"); > + return -ENOMEM; > + } > + > + return 0; > +} > + > +static void meson_mx_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) > +{ > + struct meson_mx_mmc_host *host = mmc_priv(mmc); > + struct mmc_command *cmd = mrq->cmd; > + > + if (!host->error) > + host->error = meson_mx_mmc_map_dma(mmc, mrq); > + > + if (host->error) { > + cmd->error = host->error; > + mmc_request_done(mmc, mrq); > + return; > + } > + > + host->mrq = mrq; > + > + if (mrq->data) > + writel(sg_dma_address(mrq->data->sg), > + host->base + MESON_MX_SDIO_ADDR); > + > + if (mrq->sbc) > + meson_mx_mmc_start_cmd(mmc, mrq->sbc); > + else > + meson_mx_mmc_start_cmd(mmc, mrq->cmd); > +} > + > +static int meson_mx_mmc_card_busy(struct mmc_host *mmc) > +{ > + struct meson_mx_mmc_host *host = mmc_priv(mmc); > + u32 irqc = readl(host->base + MESON_MX_SDIO_IRQC); > + > + return !!(irqc & MESON_MX_SDIO_IRQC_FORCE_DATA_DAT_MASK); > +} > + > +static void meson_mx_mmc_read_response(struct mmc_host *mmc, > + struct mmc_command *cmd) > +{ > + struct meson_mx_mmc_host *host = mmc_priv(mmc); > + u32 mult; > + int i, resp[4]; > + > + mult = readl(host->base + MESON_MX_SDIO_MULT); > + mult |= MESON_MX_SDIO_MULT_WR_RD_OUT_INDEX; > + mult &= ~MESON_MX_SDIO_MULT_RESP_READ_INDEX_MASK; > + mult |= FIELD_PREP(MESON_MX_SDIO_MULT_RESP_READ_INDEX_MASK, 0); > + writel(mult, host->base + MESON_MX_SDIO_MULT); > + > + if (cmd->flags & MMC_RSP_136) { > + for (i = 0; i <= 3; i++) > + resp[3 - i] = readl(host->base + MESON_MX_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 + MESON_MX_SDIO_ARGU); > + } > +} > + > +static irqreturn_t meson_mx_mmc_process_cmd_irq(struct meson_mx_mmc_host *host, > + u32 irqs, u32 send) > +{ > + struct mmc_command *cmd = host->cmd; > + > + /* > + * NOTE: even though it shouldn't happen we sometimes get command > + * interrupts twice (at least this is what it looks like). Ideally > + * we find out why this happens and warn here as soon as it occurs. > + */ > + if (!cmd) > + return IRQ_HANDLED; > + > + cmd->error = 0; > + meson_mx_mmc_read_response(host->mmc, cmd); > + > + if (cmd->data) { > + if (!((irqs & MESON_MX_SDIO_IRQS_DATA_READ_CRC16_OK) || > + (irqs & MESON_MX_SDIO_IRQS_DATA_WRITE_CRC16_OK))) > + cmd->error = -EILSEQ; > + } else { > + if (!((irqs & MESON_MX_SDIO_IRQS_RESP_CRC7_OK) || > + (send & MESON_MX_SDIO_SEND_RESP_WITHOUT_CRC7))) > + cmd->error = -EILSEQ; > + } > + > + return IRQ_WAKE_THREAD; > +} > + > +static irqreturn_t meson_mx_mmc_irq(int irq, void *data) > +{ > + struct meson_mx_mmc_host *host = (void *) data; > + u32 irqs, send; > + unsigned long irqflags; > + irqreturn_t ret; > + > + spin_lock_irqsave(&host->irq_lock, irqflags); > + > + irqs = readl(host->base + MESON_MX_SDIO_IRQS); > + send = readl(host->base + MESON_MX_SDIO_SEND); > + > + if (irqs & MESON_MX_SDIO_IRQS_CMD_INT) > + ret = meson_mx_mmc_process_cmd_irq(host, irqs, send); > + else > + ret = IRQ_HANDLED; > + > + /* finally ACK all pending interrupts */ > + writel(irqs, host->base + MESON_MX_SDIO_IRQS); > + > + spin_unlock_irqrestore(&host->irq_lock, irqflags); > + > + return ret; > +} > + > +static irqreturn_t meson_mx_mmc_irq_thread(int irq, void *irq_data) > +{ > + struct meson_mx_mmc_host *host = (void *) irq_data; > + struct mmc_command *cmd = host->cmd, *next_cmd; > + > + if (WARN_ON(!cmd)) > + return IRQ_HANDLED; > + > + del_timer_sync(&host->cmd_timeout); > + > + if (cmd->data) { > + dma_unmap_sg(mmc_dev(host->mmc), cmd->data->sg, > + cmd->data->sg_len, > + mmc_get_dma_dir(cmd->data)); > + > + cmd->data->bytes_xfered = cmd->data->blksz * cmd->data->blocks; > + } > + > + next_cmd = meson_mx_mmc_get_next_cmd(cmd); > + if (next_cmd) > + meson_mx_mmc_start_cmd(host->mmc, next_cmd); > + else > + meson_mx_mmc_request_done(host); > + > + return IRQ_HANDLED; > +} > + > +static void meson_mx_mmc_timeout(unsigned long arg) > +{ > + struct meson_mx_mmc_host *host = (void *) arg; > + unsigned long irqflags; > + u32 irqc; > + > + spin_lock_irqsave(&host->irq_lock, irqflags); > + > + /* disable the CMD interrupt */ > + irqc = readl(host->base + MESON_MX_SDIO_IRQC); > + irqc &= ~MESON_MX_SDIO_IRQC_ARC_CMD_INT_EN; > + writel(irqc, host->base + MESON_MX_SDIO_IRQC); > + > + spin_unlock_irqrestore(&host->irq_lock, irqflags); > + > + /* > + * skip the timeout handling if the interrupt handler already processed > + * the command. > + */ > + if (!host->cmd) > + return; > + > + dev_dbg(mmc_dev(host->mmc), > + "Timeout on CMD%u (IRQS = 0x%08x, ARGU = 0x%08x)\n", > + host->cmd->opcode, readl(host->base + MESON_MX_SDIO_IRQS), > + readl(host->base + MESON_MX_SDIO_ARGU)); > + > + host->cmd->error = -ETIMEDOUT; > + > + meson_mx_mmc_request_done(host); > +} > + > +static struct mmc_host_ops meson_mx_mmc_ops = { > + .request = meson_mx_mmc_request, > + .set_ios = meson_mx_mmc_set_ios, > + .card_busy = meson_mx_mmc_card_busy, > + .get_cd = mmc_gpio_get_cd, > + .get_ro = mmc_gpio_get_ro, > +}; > + > +static struct platform_device *meson_mx_mmc_slot_pdev(struct device *parent) > +{ > + struct device_node *slot_node; > + > + /* > + * TODO: the MMC core framework currently does not support > + * controllers with multiple slots properly. So we only register > + * the first slot for now > + */ > + slot_node = of_find_compatible_node(parent->of_node, NULL, "mmc-slot"); > + if (!slot_node) { > + dev_warn(parent, "no 'mmc-slot' sub-node found\n"); > + return ERR_PTR(-ENOENT); > + } > + > + return of_platform_device_create(slot_node, NULL, parent); > +} > + > +static int meson_mx_mmc_add_host(struct meson_mx_mmc_host *host) > +{ > + struct mmc_host *mmc = host->mmc; > + struct device *slot_dev = mmc_dev(mmc); > + int ret; > + > + if (of_property_read_u32(slot_dev->of_node, "reg", &host->slot_id)) { > + dev_err(slot_dev, "missing 'reg' property\n"); > + return -EINVAL; > + } > + > + if (host->slot_id >= MESON_MX_SDIO_MAX_SLOTS) { > + dev_err(slot_dev, "invalid 'reg' property value %d\n", > + host->slot_id); > + return -EINVAL; > + } > + > + /* Get regulators and the supported OCR mask */ > + ret = mmc_regulator_get_supply(mmc); > + if (ret == -EPROBE_DEFER) > + return ret; > + > + mmc->max_req_size = MESON_MX_SDIO_BOUNCE_REQ_SIZE; > + mmc->max_seg_size = mmc->max_req_size; > + mmc->max_blk_count = > + FIELD_GET(MESON_MX_SDIO_SEND_REPEAT_PACKAGE_TIMES_MASK, > + 0xffffffff); > + mmc->max_blk_size = FIELD_GET(MESON_MX_SDIO_EXT_DATA_RW_NUMBER_MASK, > + 0xffffffff); > + mmc->max_blk_size -= (4 * MESON_MX_SDIO_RESPONSE_CRC16_BITS); > + mmc->max_blk_size /= BITS_PER_BYTE; > + > + /* Get the min and max supported clock rates */ > + mmc->f_min = clk_round_rate(host->cfg_div_clk, 1); > + mmc->f_max = clk_round_rate(host->cfg_div_clk, > + clk_get_rate(host->parent_clk)); > + > + mmc->caps |= MMC_CAP_ERASE | MMC_CAP_CMD23; > + mmc->ops = &meson_mx_mmc_ops; > + > + ret = mmc_of_parse(mmc); > + if (ret) > + return ret; > + > + ret = mmc_add_host(mmc); > + if (ret) > + return ret; > + > + return 0; > +} > + > +static int meson_mx_mmc_register_clks(struct meson_mx_mmc_host *host) > +{ > + struct clk_init_data init; > + const char *clk_div_parent, *clk_fixed_factor_parent; > + > + clk_fixed_factor_parent = __clk_get_name(host->parent_clk); > + init.name = devm_kasprintf(host->controller_dev, GFP_KERNEL, > + "%s#fixed_factor", > + dev_name(host->controller_dev)); > + init.ops = &clk_fixed_factor_ops; > + init.flags = 0; > + init.parent_names = &clk_fixed_factor_parent; > + init.num_parents = 1; > + host->fixed_factor.div = 2; > + host->fixed_factor.mult = 1; > + host->fixed_factor.hw.init = &init; > + > + host->fixed_factor_clk = devm_clk_register(host->controller_dev, > + &host->fixed_factor.hw); > + if (WARN_ON(PTR_ERR_OR_ZERO(host->fixed_factor_clk))) > + return PTR_ERR(host->fixed_factor_clk); > + > + clk_div_parent = __clk_get_name(host->fixed_factor_clk); > + init.name = devm_kasprintf(host->controller_dev, GFP_KERNEL, > + "%s#div", dev_name(host->controller_dev)); > + init.ops = &clk_divider_ops; > + init.flags = CLK_SET_RATE_PARENT; > + init.parent_names = &clk_div_parent; > + init.num_parents = 1; > + host->cfg_div.reg = host->base + MESON_MX_SDIO_CONF; > + host->cfg_div.shift = MESON_MX_SDIO_CONF_CMD_CLK_DIV_SHIFT; > + host->cfg_div.width = MESON_MX_SDIO_CONF_CMD_CLK_DIV_WIDTH; > + host->cfg_div.hw.init = &init; > + host->cfg_div.flags = CLK_DIVIDER_ALLOW_ZERO; > + > + host->cfg_div_clk = devm_clk_register(host->controller_dev, > + &host->cfg_div.hw); > + if (WARN_ON(PTR_ERR_OR_ZERO(host->cfg_div_clk))) > + return PTR_ERR(host->fixed_factor_clk); > + > + return 0; > +} > + > +static int meson_mx_mmc_probe(struct platform_device *pdev) > +{ > + struct platform_device *slot_pdev; > + struct mmc_host *mmc; > + struct meson_mx_mmc_host *host; > + struct resource *res; > + int ret, irq; > + u32 conf; > + > + slot_pdev = meson_mx_mmc_slot_pdev(&pdev->dev); > + if (!slot_pdev) > + return -ENODEV; > + else if (IS_ERR(slot_pdev)) > + return PTR_ERR(slot_pdev); > + > + mmc = mmc_alloc_host(sizeof(*host), &slot_pdev->dev); > + if (!mmc) { > + ret = -ENOMEM; > + goto error_unregister_slot_pdev; > + } > + > + host = mmc_priv(mmc); > + host->mmc = mmc; > + host->controller_dev = &pdev->dev; > + > + spin_lock_init(&host->irq_lock); > + setup_timer(&host->cmd_timeout, meson_mx_mmc_timeout, > + (unsigned long)host); > + > + platform_set_drvdata(pdev, host); > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + host->base = devm_ioremap_resource(host->controller_dev, res); > + if (IS_ERR(host->base)) { > + ret = PTR_ERR(host->base); > + goto error_free_mmc; > + } > + > + irq = platform_get_irq(pdev, 0); > + ret = devm_request_threaded_irq(host->controller_dev, irq, > + meson_mx_mmc_irq, > + meson_mx_mmc_irq_thread, IRQF_ONESHOT, > + NULL, host); > + if (ret) > + goto error_free_mmc; > + > + host->core_clk = devm_clk_get(host->controller_dev, "core"); > + if (IS_ERR(host->core_clk)) { > + ret = PTR_ERR(host->core_clk); > + goto error_free_mmc; > + } > + > + host->parent_clk = devm_clk_get(host->controller_dev, "clkin"); > + if (IS_ERR(host->parent_clk)) { > + ret = PTR_ERR(host->parent_clk); > + goto error_free_mmc; > + } > + > + ret = meson_mx_mmc_register_clks(host); > + if (ret) > + goto error_free_mmc; > + > + ret = clk_prepare_enable(host->core_clk); > + if (ret) { > + dev_err(host->controller_dev, "Failed to enable core clock\n"); > + goto error_free_mmc; > + } > + > + ret = clk_prepare_enable(host->cfg_div_clk); > + if (ret) { > + dev_err(host->controller_dev, "Failed to enable MMC clock\n"); > + goto error_disable_core_clk; > + } > + > + conf = 0; > + conf |= FIELD_PREP(MESON_MX_SDIO_CONF_CMD_ARGUMENT_BITS_MASK, 39); > + conf |= FIELD_PREP(MESON_MX_SDIO_CONF_M_ENDIAN_MASK, 0x3); > + conf |= FIELD_PREP(MESON_MX_SDIO_CONF_WRITE_NWR_MASK, 0x2); > + conf |= FIELD_PREP(MESON_MX_SDIO_CONF_WRITE_CRC_OK_STATUS_MASK, 0x2); > + writel(conf, host->base + MESON_MX_SDIO_CONF); > + > + meson_mx_mmc_soft_reset(host); > + > + ret = meson_mx_mmc_add_host(host); > + if (ret) > + goto error_disable_clks; > + > + return 0; > + > +error_disable_clks: > + clk_disable_unprepare(host->cfg_div_clk); > +error_disable_core_clk: > + clk_disable_unprepare(host->core_clk); > +error_free_mmc: > + mmc_free_host(mmc); > +error_unregister_slot_pdev: > + of_platform_device_destroy(&slot_pdev->dev, NULL); > + return ret; > +} > + > +static int meson_mx_mmc_remove(struct platform_device *pdev) > +{ > + struct meson_mx_mmc_host *host = platform_get_drvdata(pdev); > + struct device *slot_dev = mmc_dev(host->mmc); > + > + del_timer_sync(&host->cmd_timeout); > + > + mmc_remove_host(host->mmc); > + > + of_platform_device_destroy(slot_dev, NULL); > + > + clk_disable_unprepare(host->cfg_div_clk); > + clk_disable_unprepare(host->core_clk); > + > + mmc_free_host(host->mmc); > + > + return 0; > +} > + > +static const struct of_device_id meson_mx_mmc_of_match[] = { > + { .compatible = "amlogic,meson8-sdio", }, > + { .compatible = "amlogic,meson8b-sdio", }, > + { /* sentinel */ } > +}; > +MODULE_DEVICE_TABLE(of, meson_mx_mmc_of_match); > + > +static struct platform_driver meson_mx_mmc_driver = { > + .probe = meson_mx_mmc_probe, > + .remove = meson_mx_mmc_remove, > + .driver = { > + .name = "meson-mx-sdio", > + .of_match_table = of_match_ptr(meson_mx_mmc_of_match), > + }, > +}; > + > +module_platform_driver(meson_mx_mmc_driver); > + > +MODULE_DESCRIPTION("Meson6, Meson8 and Meson8b SDIO/MMC Host Driver"); > +MODULE_AUTHOR("Carlo Caione <carlo@endlessm.com>"); > +MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>"); > +MODULE_LICENSE("GPL v2"); > -- > 2.14.2 > -- To unsubscribe from this list: send the line "unsubscribe linux-mmc" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 8c15637178ff..a27b99c77661 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -352,6 +352,19 @@ config MMC_MESON_GX If you have a controller with this interface, say Y here. +config MMC_MESON_MX_SDIO + tristate "Amlogic Meson6/Meson8/Meson8b SD/MMC Host Controller support" + depends on ARCH_MESON || COMPILE_TEST + depends on COMMON_CLK + depends on HAS_DMA + depends on OF + help + This selects support for the SD/MMC Host Controller on + Amlogic Meson6, Meson8 and Meson8b SoCs. + + If you have a controller with this interface, say Y or M here. + If unsure, say N. + config MMC_MOXART tristate "MOXART SD/MMC Host Controller support" depends on ARCH_MOXART && MMC diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 303f5cd46cd9..0de72a531240 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -64,6 +64,7 @@ obj-$(CONFIG_MMC_VUB300) += vub300.o obj-$(CONFIG_MMC_USHC) += ushc.o obj-$(CONFIG_MMC_WMT) += wmt-sdmmc.o obj-$(CONFIG_MMC_MESON_GX) += meson-gx-mmc.o +obj-$(CONFIG_MMC_MESON_MX_SDIO) += meson-mx-sdio.o obj-$(CONFIG_MMC_MOXART) += moxart-mmc.o obj-$(CONFIG_MMC_SUNXI) += sunxi-mmc.o obj-$(CONFIG_MMC_USDHI6ROL0) += usdhi6rol0.o diff --git a/drivers/mmc/host/meson-mx-sdio.c b/drivers/mmc/host/meson-mx-sdio.c new file mode 100644 index 000000000000..19b499bbe691 --- /dev/null +++ b/drivers/mmc/host/meson-mx-sdio.c @@ -0,0 +1,769 @@ +/* + * meson-mx-sdio.c - Meson6, Meson8 and Meson8b SDIO/MMC Host Controller + * + * Copyright (C) 2015 Endless Mobile, Inc. + * Author: Carlo Caione <carlo@endlessm.com> + * Copyright (C) 2017 Martin Blumenstingl <martin.blumenstingl@googlemail.com> + * + * 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 <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/platform_device.h> +#include <linux/of_platform.h> +#include <linux/timer.h> +#include <linux/types.h> + +#include <linux/mmc/host.h> +#include <linux/mmc/mmc.h> +#include <linux/mmc/sdio.h> +#include <linux/mmc/slot-gpio.h> + +#define MESON_MX_SDIO_ARGU 0x00 + +#define MESON_MX_SDIO_SEND 0x04 + #define MESON_MX_SDIO_SEND_COMMAND_INDEX_MASK GENMASK(7, 0) + #define MESON_MX_SDIO_SEND_CMD_RESP_BITS_MASK GENMASK(15, 8) + #define MESON_MX_SDIO_SEND_RESP_WITHOUT_CRC7 BIT(16) + #define MESON_MX_SDIO_SEND_RESP_HAS_DATA BIT(17) + #define MESON_MX_SDIO_SEND_RESP_CRC7_FROM_8 BIT(18) + #define MESON_MX_SDIO_SEND_CHECK_DAT0_BUSY BIT(19) + #define MESON_MX_SDIO_SEND_DATA BIT(20) + #define MESON_MX_SDIO_SEND_USE_INT_WINDOW BIT(21) + #define MESON_MX_SDIO_SEND_REPEAT_PACKAGE_TIMES_MASK GENMASK(31, 24) + +#define MESON_MX_SDIO_CONF 0x08 + #define MESON_MX_SDIO_CONF_CMD_CLK_DIV_SHIFT 0 + #define MESON_MX_SDIO_CONF_CMD_CLK_DIV_WIDTH 10 + #define MESON_MX_SDIO_CONF_CMD_DISABLE_CRC BIT(10) + #define MESON_MX_SDIO_CONF_CMD_OUT_AT_POSITIVE_EDGE BIT(11) + #define MESON_MX_SDIO_CONF_CMD_ARGUMENT_BITS_MASK GENMASK(17, 12) + #define MESON_MX_SDIO_CONF_RESP_LATCH_AT_NEGATIVE_EDGE BIT(18) + #define MESON_MX_SDIO_CONF_DATA_LATCH_AT_NEGATIVE_EDGE BIT(19) + #define MESON_MX_SDIO_CONF_BUS_WIDTH BIT(20) + #define MESON_MX_SDIO_CONF_M_ENDIAN_MASK GENMASK(22, 21) + #define MESON_MX_SDIO_CONF_WRITE_NWR_MASK GENMASK(28, 23) + #define MESON_MX_SDIO_CONF_WRITE_CRC_OK_STATUS_MASK GENMASK(31, 29) + +#define MESON_MX_SDIO_IRQS 0x0c + #define MESON_MX_SDIO_IRQS_STATUS_STATE_MACHINE_MASK GENMASK(3, 0) + #define MESON_MX_SDIO_IRQS_CMD_BUSY BIT(4) + #define MESON_MX_SDIO_IRQS_RESP_CRC7_OK BIT(5) + #define MESON_MX_SDIO_IRQS_DATA_READ_CRC16_OK BIT(6) + #define MESON_MX_SDIO_IRQS_DATA_WRITE_CRC16_OK BIT(7) + #define MESON_MX_SDIO_IRQS_IF_INT BIT(8) + #define MESON_MX_SDIO_IRQS_CMD_INT BIT(9) + #define MESON_MX_SDIO_IRQS_STATUS_INFO_MASK GENMASK(15, 12) + #define MESON_MX_SDIO_IRQS_TIMING_OUT_INT BIT(16) + #define MESON_MX_SDIO_IRQS_AMRISC_TIMING_OUT_INT_EN BIT(17) + #define MESON_MX_SDIO_IRQS_ARC_TIMING_OUT_INT_EN BIT(18) + #define MESON_MX_SDIO_IRQS_TIMING_OUT_COUNT_MASK GENMASK(31, 19) + +#define MESON_MX_SDIO_IRQC 0x10 + #define MESON_MX_SDIO_IRQC_ARC_IF_INT_EN BIT(3) + #define MESON_MX_SDIO_IRQC_ARC_CMD_INT_EN BIT(4) + #define MESON_MX_SDIO_IRQC_IF_CONFIG_MASK GENMASK(7, 6) + #define MESON_MX_SDIO_IRQC_FORCE_DATA_CLK BIT(8) + #define MESON_MX_SDIO_IRQC_FORCE_DATA_CMD BIT(9) + #define MESON_MX_SDIO_IRQC_FORCE_DATA_DAT_MASK GENMASK(10, 13) + #define MESON_MX_SDIO_IRQC_SOFT_RESET BIT(15) + #define MESON_MX_SDIO_IRQC_FORCE_HALT BIT(30) + #define MESON_MX_SDIO_IRQC_HALT_HOLE BIT(31) + +#define MESON_MX_SDIO_MULT 0x14 + #define MESON_MX_SDIO_MULT_PORT_SEL_MASK GENMASK(1, 0) + #define MESON_MX_SDIO_MULT_MEMORY_STICK_ENABLE BIT(2) + #define MESON_MX_SDIO_MULT_MEMORY_STICK_SCLK_ALWAYS BIT(3) + #define MESON_MX_SDIO_MULT_STREAM_ENABLE BIT(4) + #define MESON_MX_SDIO_MULT_STREAM_8BITS_MODE BIT(5) + #define MESON_MX_SDIO_MULT_WR_RD_OUT_INDEX BIT(8) + #define MESON_MX_SDIO_MULT_DAT0_DAT1_SWAPPED BIT(10) + #define MESON_MX_SDIO_MULT_DAT1_DAT0_SWAPPED BIT(11) + #define MESON_MX_SDIO_MULT_RESP_READ_INDEX_MASK GENMASK(15, 12) + +#define MESON_MX_SDIO_ADDR 0x18 + +#define MESON_MX_SDIO_EXT 0x1c + #define MESON_MX_SDIO_EXT_DATA_RW_NUMBER_MASK GENMASK(29, 16) + +#define MESON_MX_SDIO_BOUNCE_REQ_SIZE (128 * 1024) +#define MESON_MX_SDIO_RESPONSE_CRC16_BITS (16 - 1) +#define MESON_MX_SDIO_MAX_SLOTS 3 + +struct meson_mx_mmc_host { + struct device *controller_dev; + + struct clk *parent_clk; + struct clk *core_clk; + struct clk_divider cfg_div; + struct clk *cfg_div_clk; + struct clk_fixed_factor fixed_factor; + struct clk *fixed_factor_clk; + + void __iomem *base; + int irq; + spinlock_t irq_lock; + + struct timer_list cmd_timeout; + + unsigned int slot_id; + struct mmc_host *mmc; + + struct mmc_request *mrq; + struct mmc_command *cmd; + int error; +}; + +static void meson_mx_mmc_mask_bits(struct mmc_host *mmc, char reg, u32 mask, + u32 val) +{ + struct meson_mx_mmc_host *host = mmc_priv(mmc); + u32 regval; + + regval = readl(host->base + reg); + regval &= ~mask; + regval |= (val & mask); + + writel(regval, host->base + reg); +} + +static void meson_mx_mmc_soft_reset(struct meson_mx_mmc_host *host) +{ + writel(MESON_MX_SDIO_IRQC_SOFT_RESET, host->base + MESON_MX_SDIO_IRQC); + udelay(2); +} + +static struct mmc_command *meson_mx_mmc_get_next_cmd(struct mmc_command *cmd) +{ + if (cmd->opcode == MMC_SET_BLOCK_COUNT && !cmd->error) + return cmd->mrq->cmd; + else if (mmc_op_multi(cmd->opcode) && + (!cmd->mrq->sbc || cmd->error || cmd->data->error)) + return cmd->mrq->stop; + else + return NULL; +} + +static void meson_mx_mmc_start_cmd(struct mmc_host *mmc, + struct mmc_command *cmd) +{ + struct meson_mx_mmc_host *host = mmc_priv(mmc); + unsigned int pack_size; + unsigned long irqflags, timeout; + u32 mult, send = 0, ext = 0; + + host->cmd = cmd; + + if (cmd->busy_timeout) + timeout = msecs_to_jiffies(cmd->busy_timeout); + else + timeout = msecs_to_jiffies(1000); + + switch (mmc_resp_type(cmd)) { + case MMC_RSP_R1: + case MMC_RSP_R1B: + case MMC_RSP_R3: + /* 7 (CMD) + 32 (response) + 7 (CRC) -1 */ + send |= FIELD_PREP(MESON_MX_SDIO_SEND_CMD_RESP_BITS_MASK, 45); + break; + case MMC_RSP_R2: + /* 7 (CMD) + 120 (response) + 7 (CRC) -1 */ + send |= FIELD_PREP(MESON_MX_SDIO_SEND_CMD_RESP_BITS_MASK, 133); + send |= MESON_MX_SDIO_SEND_RESP_CRC7_FROM_8; + break; + default: + break; + } + + if (!(cmd->flags & MMC_RSP_CRC)) + send |= MESON_MX_SDIO_SEND_RESP_WITHOUT_CRC7; + + if (cmd->flags & MMC_RSP_BUSY) + send |= MESON_MX_SDIO_SEND_CHECK_DAT0_BUSY; + + if (cmd->data) { + send |= FIELD_PREP(MESON_MX_SDIO_SEND_REPEAT_PACKAGE_TIMES_MASK, + (cmd->data->blocks - 1)); + + pack_size = cmd->data->blksz * BITS_PER_BYTE; + if (mmc->ios.bus_width == MMC_BUS_WIDTH_4) + pack_size += MESON_MX_SDIO_RESPONSE_CRC16_BITS * 4; + else + pack_size += MESON_MX_SDIO_RESPONSE_CRC16_BITS * 1; + + ext |= FIELD_PREP(MESON_MX_SDIO_EXT_DATA_RW_NUMBER_MASK, + pack_size); + + if (cmd->data->flags & MMC_DATA_WRITE) + send |= MESON_MX_SDIO_SEND_DATA; + else + send |= MESON_MX_SDIO_SEND_RESP_HAS_DATA; + + cmd->data->bytes_xfered = 0; + } + + send |= FIELD_PREP(MESON_MX_SDIO_SEND_COMMAND_INDEX_MASK, + (0x40 | cmd->opcode)); + + spin_lock_irqsave(&host->irq_lock, irqflags); + + mult = readl(host->base + MESON_MX_SDIO_MULT); + mult &= ~MESON_MX_SDIO_MULT_PORT_SEL_MASK; + mult |= FIELD_PREP(MESON_MX_SDIO_MULT_PORT_SEL_MASK, host->slot_id); + mult |= BIT(31); + writel(mult, host->base + MESON_MX_SDIO_MULT); + + /* enable the CMD done interrupt */ + meson_mx_mmc_mask_bits(mmc, MESON_MX_SDIO_IRQC, + MESON_MX_SDIO_IRQC_ARC_CMD_INT_EN, + MESON_MX_SDIO_IRQC_ARC_CMD_INT_EN); + + /* clear pending interrupts */ + meson_mx_mmc_mask_bits(mmc, MESON_MX_SDIO_IRQS, + MESON_MX_SDIO_IRQS_CMD_INT, + MESON_MX_SDIO_IRQS_CMD_INT); + + writel(cmd->arg, host->base + MESON_MX_SDIO_ARGU); + writel(ext, host->base + MESON_MX_SDIO_EXT); + writel(send, host->base + MESON_MX_SDIO_SEND); + + spin_unlock_irqrestore(&host->irq_lock, irqflags); + + mod_timer(&host->cmd_timeout, jiffies + timeout); +} + +static void meson_mx_mmc_request_done(struct meson_mx_mmc_host *host) +{ + struct mmc_request *mrq; + + mrq = host->mrq; + + host->mrq = NULL; + host->cmd = NULL; + + mmc_request_done(host->mmc, mrq); +} + +static void meson_mx_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct meson_mx_mmc_host *host = mmc_priv(mmc); + unsigned short vdd = ios->vdd; + unsigned long clk_rate = ios->clock; + + switch (ios->bus_width) { + case MMC_BUS_WIDTH_1: + meson_mx_mmc_mask_bits(mmc, MESON_MX_SDIO_CONF, + MESON_MX_SDIO_CONF_BUS_WIDTH, 0); + break; + + case MMC_BUS_WIDTH_4: + meson_mx_mmc_mask_bits(mmc, MESON_MX_SDIO_CONF, + MESON_MX_SDIO_CONF_BUS_WIDTH, + MESON_MX_SDIO_CONF_BUS_WIDTH); + break; + + case MMC_BUS_WIDTH_8: + default: + dev_err(mmc_dev(mmc), "unsupported bus width: %d\n", + ios->bus_width); + host->error = -EINVAL; + return; + } + + host->error = clk_set_rate(host->cfg_div_clk, ios->clock); + if (host->error) { + dev_warn(mmc_dev(mmc), + "failed to set MMC clock to %lu: %d\n", + clk_rate, host->error); + return; + } + + mmc->actual_clock = clk_get_rate(host->cfg_div_clk); + + switch (ios->power_mode) { + case MMC_POWER_OFF: + vdd = 0; + /* fall-through: */ + case MMC_POWER_UP: + if (!IS_ERR(mmc->supply.vmmc)) { + host->error = mmc_regulator_set_ocr(mmc, + mmc->supply.vmmc, + vdd); + if (host->error) + return; + } + break; + } +} + +static int meson_mx_mmc_map_dma(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct mmc_data *data = mrq->data; + int dma_len; + struct scatterlist *sg; + + if (!data) + return 0; + + sg = data->sg; + if (sg->offset & 3 || sg->length & 3) { + dev_err(mmc_dev(mmc), + "unaligned scatterlist: offset %x length %d\n", + sg->offset, sg->length); + return -EINVAL; + } + + dma_len = dma_map_sg(mmc_dev(mmc), data->sg, data->sg_len, + mmc_get_dma_dir(data)); + if (dma_len <= 0) { + dev_err(mmc_dev(mmc), "dma_map_sg failed\n"); + return -ENOMEM; + } + + return 0; +} + +static void meson_mx_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct meson_mx_mmc_host *host = mmc_priv(mmc); + struct mmc_command *cmd = mrq->cmd; + + if (!host->error) + host->error = meson_mx_mmc_map_dma(mmc, mrq); + + if (host->error) { + cmd->error = host->error; + mmc_request_done(mmc, mrq); + return; + } + + host->mrq = mrq; + + if (mrq->data) + writel(sg_dma_address(mrq->data->sg), + host->base + MESON_MX_SDIO_ADDR); + + if (mrq->sbc) + meson_mx_mmc_start_cmd(mmc, mrq->sbc); + else + meson_mx_mmc_start_cmd(mmc, mrq->cmd); +} + +static int meson_mx_mmc_card_busy(struct mmc_host *mmc) +{ + struct meson_mx_mmc_host *host = mmc_priv(mmc); + u32 irqc = readl(host->base + MESON_MX_SDIO_IRQC); + + return !!(irqc & MESON_MX_SDIO_IRQC_FORCE_DATA_DAT_MASK); +} + +static void meson_mx_mmc_read_response(struct mmc_host *mmc, + struct mmc_command *cmd) +{ + struct meson_mx_mmc_host *host = mmc_priv(mmc); + u32 mult; + int i, resp[4]; + + mult = readl(host->base + MESON_MX_SDIO_MULT); + mult |= MESON_MX_SDIO_MULT_WR_RD_OUT_INDEX; + mult &= ~MESON_MX_SDIO_MULT_RESP_READ_INDEX_MASK; + mult |= FIELD_PREP(MESON_MX_SDIO_MULT_RESP_READ_INDEX_MASK, 0); + writel(mult, host->base + MESON_MX_SDIO_MULT); + + if (cmd->flags & MMC_RSP_136) { + for (i = 0; i <= 3; i++) + resp[3 - i] = readl(host->base + MESON_MX_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 + MESON_MX_SDIO_ARGU); + } +} + +static irqreturn_t meson_mx_mmc_process_cmd_irq(struct meson_mx_mmc_host *host, + u32 irqs, u32 send) +{ + struct mmc_command *cmd = host->cmd; + + /* + * NOTE: even though it shouldn't happen we sometimes get command + * interrupts twice (at least this is what it looks like). Ideally + * we find out why this happens and warn here as soon as it occurs. + */ + if (!cmd) + return IRQ_HANDLED; + + cmd->error = 0; + meson_mx_mmc_read_response(host->mmc, cmd); + + if (cmd->data) { + if (!((irqs & MESON_MX_SDIO_IRQS_DATA_READ_CRC16_OK) || + (irqs & MESON_MX_SDIO_IRQS_DATA_WRITE_CRC16_OK))) + cmd->error = -EILSEQ; + } else { + if (!((irqs & MESON_MX_SDIO_IRQS_RESP_CRC7_OK) || + (send & MESON_MX_SDIO_SEND_RESP_WITHOUT_CRC7))) + cmd->error = -EILSEQ; + } + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t meson_mx_mmc_irq(int irq, void *data) +{ + struct meson_mx_mmc_host *host = (void *) data; + u32 irqs, send; + unsigned long irqflags; + irqreturn_t ret; + + spin_lock_irqsave(&host->irq_lock, irqflags); + + irqs = readl(host->base + MESON_MX_SDIO_IRQS); + send = readl(host->base + MESON_MX_SDIO_SEND); + + if (irqs & MESON_MX_SDIO_IRQS_CMD_INT) + ret = meson_mx_mmc_process_cmd_irq(host, irqs, send); + else + ret = IRQ_HANDLED; + + /* finally ACK all pending interrupts */ + writel(irqs, host->base + MESON_MX_SDIO_IRQS); + + spin_unlock_irqrestore(&host->irq_lock, irqflags); + + return ret; +} + +static irqreturn_t meson_mx_mmc_irq_thread(int irq, void *irq_data) +{ + struct meson_mx_mmc_host *host = (void *) irq_data; + struct mmc_command *cmd = host->cmd, *next_cmd; + + if (WARN_ON(!cmd)) + return IRQ_HANDLED; + + del_timer_sync(&host->cmd_timeout); + + if (cmd->data) { + dma_unmap_sg(mmc_dev(host->mmc), cmd->data->sg, + cmd->data->sg_len, + mmc_get_dma_dir(cmd->data)); + + cmd->data->bytes_xfered = cmd->data->blksz * cmd->data->blocks; + } + + next_cmd = meson_mx_mmc_get_next_cmd(cmd); + if (next_cmd) + meson_mx_mmc_start_cmd(host->mmc, next_cmd); + else + meson_mx_mmc_request_done(host); + + return IRQ_HANDLED; +} + +static void meson_mx_mmc_timeout(unsigned long arg) +{ + struct meson_mx_mmc_host *host = (void *) arg; + unsigned long irqflags; + u32 irqc; + + spin_lock_irqsave(&host->irq_lock, irqflags); + + /* disable the CMD interrupt */ + irqc = readl(host->base + MESON_MX_SDIO_IRQC); + irqc &= ~MESON_MX_SDIO_IRQC_ARC_CMD_INT_EN; + writel(irqc, host->base + MESON_MX_SDIO_IRQC); + + spin_unlock_irqrestore(&host->irq_lock, irqflags); + + /* + * skip the timeout handling if the interrupt handler already processed + * the command. + */ + if (!host->cmd) + return; + + dev_dbg(mmc_dev(host->mmc), + "Timeout on CMD%u (IRQS = 0x%08x, ARGU = 0x%08x)\n", + host->cmd->opcode, readl(host->base + MESON_MX_SDIO_IRQS), + readl(host->base + MESON_MX_SDIO_ARGU)); + + host->cmd->error = -ETIMEDOUT; + + meson_mx_mmc_request_done(host); +} + +static struct mmc_host_ops meson_mx_mmc_ops = { + .request = meson_mx_mmc_request, + .set_ios = meson_mx_mmc_set_ios, + .card_busy = meson_mx_mmc_card_busy, + .get_cd = mmc_gpio_get_cd, + .get_ro = mmc_gpio_get_ro, +}; + +static struct platform_device *meson_mx_mmc_slot_pdev(struct device *parent) +{ + struct device_node *slot_node; + + /* + * TODO: the MMC core framework currently does not support + * controllers with multiple slots properly. So we only register + * the first slot for now + */ + slot_node = of_find_compatible_node(parent->of_node, NULL, "mmc-slot"); + if (!slot_node) { + dev_warn(parent, "no 'mmc-slot' sub-node found\n"); + return ERR_PTR(-ENOENT); + } + + return of_platform_device_create(slot_node, NULL, parent); +} + +static int meson_mx_mmc_add_host(struct meson_mx_mmc_host *host) +{ + struct mmc_host *mmc = host->mmc; + struct device *slot_dev = mmc_dev(mmc); + int ret; + + if (of_property_read_u32(slot_dev->of_node, "reg", &host->slot_id)) { + dev_err(slot_dev, "missing 'reg' property\n"); + return -EINVAL; + } + + if (host->slot_id >= MESON_MX_SDIO_MAX_SLOTS) { + dev_err(slot_dev, "invalid 'reg' property value %d\n", + host->slot_id); + return -EINVAL; + } + + /* Get regulators and the supported OCR mask */ + ret = mmc_regulator_get_supply(mmc); + if (ret == -EPROBE_DEFER) + return ret; + + mmc->max_req_size = MESON_MX_SDIO_BOUNCE_REQ_SIZE; + mmc->max_seg_size = mmc->max_req_size; + mmc->max_blk_count = + FIELD_GET(MESON_MX_SDIO_SEND_REPEAT_PACKAGE_TIMES_MASK, + 0xffffffff); + mmc->max_blk_size = FIELD_GET(MESON_MX_SDIO_EXT_DATA_RW_NUMBER_MASK, + 0xffffffff); + mmc->max_blk_size -= (4 * MESON_MX_SDIO_RESPONSE_CRC16_BITS); + mmc->max_blk_size /= BITS_PER_BYTE; + + /* Get the min and max supported clock rates */ + mmc->f_min = clk_round_rate(host->cfg_div_clk, 1); + mmc->f_max = clk_round_rate(host->cfg_div_clk, + clk_get_rate(host->parent_clk)); + + mmc->caps |= MMC_CAP_ERASE | MMC_CAP_CMD23; + mmc->ops = &meson_mx_mmc_ops; + + ret = mmc_of_parse(mmc); + if (ret) + return ret; + + ret = mmc_add_host(mmc); + if (ret) + return ret; + + return 0; +} + +static int meson_mx_mmc_register_clks(struct meson_mx_mmc_host *host) +{ + struct clk_init_data init; + const char *clk_div_parent, *clk_fixed_factor_parent; + + clk_fixed_factor_parent = __clk_get_name(host->parent_clk); + init.name = devm_kasprintf(host->controller_dev, GFP_KERNEL, + "%s#fixed_factor", + dev_name(host->controller_dev)); + init.ops = &clk_fixed_factor_ops; + init.flags = 0; + init.parent_names = &clk_fixed_factor_parent; + init.num_parents = 1; + host->fixed_factor.div = 2; + host->fixed_factor.mult = 1; + host->fixed_factor.hw.init = &init; + + host->fixed_factor_clk = devm_clk_register(host->controller_dev, + &host->fixed_factor.hw); + if (WARN_ON(PTR_ERR_OR_ZERO(host->fixed_factor_clk))) + return PTR_ERR(host->fixed_factor_clk); + + clk_div_parent = __clk_get_name(host->fixed_factor_clk); + init.name = devm_kasprintf(host->controller_dev, GFP_KERNEL, + "%s#div", dev_name(host->controller_dev)); + init.ops = &clk_divider_ops; + init.flags = CLK_SET_RATE_PARENT; + init.parent_names = &clk_div_parent; + init.num_parents = 1; + host->cfg_div.reg = host->base + MESON_MX_SDIO_CONF; + host->cfg_div.shift = MESON_MX_SDIO_CONF_CMD_CLK_DIV_SHIFT; + host->cfg_div.width = MESON_MX_SDIO_CONF_CMD_CLK_DIV_WIDTH; + host->cfg_div.hw.init = &init; + host->cfg_div.flags = CLK_DIVIDER_ALLOW_ZERO; + + host->cfg_div_clk = devm_clk_register(host->controller_dev, + &host->cfg_div.hw); + if (WARN_ON(PTR_ERR_OR_ZERO(host->cfg_div_clk))) + return PTR_ERR(host->fixed_factor_clk); + + return 0; +} + +static int meson_mx_mmc_probe(struct platform_device *pdev) +{ + struct platform_device *slot_pdev; + struct mmc_host *mmc; + struct meson_mx_mmc_host *host; + struct resource *res; + int ret, irq; + u32 conf; + + slot_pdev = meson_mx_mmc_slot_pdev(&pdev->dev); + if (!slot_pdev) + return -ENODEV; + else if (IS_ERR(slot_pdev)) + return PTR_ERR(slot_pdev); + + mmc = mmc_alloc_host(sizeof(*host), &slot_pdev->dev); + if (!mmc) { + ret = -ENOMEM; + goto error_unregister_slot_pdev; + } + + host = mmc_priv(mmc); + host->mmc = mmc; + host->controller_dev = &pdev->dev; + + spin_lock_init(&host->irq_lock); + setup_timer(&host->cmd_timeout, meson_mx_mmc_timeout, + (unsigned long)host); + + platform_set_drvdata(pdev, host); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + host->base = devm_ioremap_resource(host->controller_dev, res); + if (IS_ERR(host->base)) { + ret = PTR_ERR(host->base); + goto error_free_mmc; + } + + irq = platform_get_irq(pdev, 0); + ret = devm_request_threaded_irq(host->controller_dev, irq, + meson_mx_mmc_irq, + meson_mx_mmc_irq_thread, IRQF_ONESHOT, + NULL, host); + if (ret) + goto error_free_mmc; + + host->core_clk = devm_clk_get(host->controller_dev, "core"); + if (IS_ERR(host->core_clk)) { + ret = PTR_ERR(host->core_clk); + goto error_free_mmc; + } + + host->parent_clk = devm_clk_get(host->controller_dev, "clkin"); + if (IS_ERR(host->parent_clk)) { + ret = PTR_ERR(host->parent_clk); + goto error_free_mmc; + } + + ret = meson_mx_mmc_register_clks(host); + if (ret) + goto error_free_mmc; + + ret = clk_prepare_enable(host->core_clk); + if (ret) { + dev_err(host->controller_dev, "Failed to enable core clock\n"); + goto error_free_mmc; + } + + ret = clk_prepare_enable(host->cfg_div_clk); + if (ret) { + dev_err(host->controller_dev, "Failed to enable MMC clock\n"); + goto error_disable_core_clk; + } + + conf = 0; + conf |= FIELD_PREP(MESON_MX_SDIO_CONF_CMD_ARGUMENT_BITS_MASK, 39); + conf |= FIELD_PREP(MESON_MX_SDIO_CONF_M_ENDIAN_MASK, 0x3); + conf |= FIELD_PREP(MESON_MX_SDIO_CONF_WRITE_NWR_MASK, 0x2); + conf |= FIELD_PREP(MESON_MX_SDIO_CONF_WRITE_CRC_OK_STATUS_MASK, 0x2); + writel(conf, host->base + MESON_MX_SDIO_CONF); + + meson_mx_mmc_soft_reset(host); + + ret = meson_mx_mmc_add_host(host); + if (ret) + goto error_disable_clks; + + return 0; + +error_disable_clks: + clk_disable_unprepare(host->cfg_div_clk); +error_disable_core_clk: + clk_disable_unprepare(host->core_clk); +error_free_mmc: + mmc_free_host(mmc); +error_unregister_slot_pdev: + of_platform_device_destroy(&slot_pdev->dev, NULL); + return ret; +} + +static int meson_mx_mmc_remove(struct platform_device *pdev) +{ + struct meson_mx_mmc_host *host = platform_get_drvdata(pdev); + struct device *slot_dev = mmc_dev(host->mmc); + + del_timer_sync(&host->cmd_timeout); + + mmc_remove_host(host->mmc); + + of_platform_device_destroy(slot_dev, NULL); + + clk_disable_unprepare(host->cfg_div_clk); + clk_disable_unprepare(host->core_clk); + + mmc_free_host(host->mmc); + + return 0; +} + +static const struct of_device_id meson_mx_mmc_of_match[] = { + { .compatible = "amlogic,meson8-sdio", }, + { .compatible = "amlogic,meson8b-sdio", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, meson_mx_mmc_of_match); + +static struct platform_driver meson_mx_mmc_driver = { + .probe = meson_mx_mmc_probe, + .remove = meson_mx_mmc_remove, + .driver = { + .name = "meson-mx-sdio", + .of_match_table = of_match_ptr(meson_mx_mmc_of_match), + }, +}; + +module_platform_driver(meson_mx_mmc_driver); + +MODULE_DESCRIPTION("Meson6, Meson8 and Meson8b SDIO/MMC Host Driver"); +MODULE_AUTHOR("Carlo Caione <carlo@endlessm.com>"); +MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>"); +MODULE_LICENSE("GPL v2");