[PATCH v8] mmc: moxart: Add MOXA ART SD/MMC driver
Ulf Hansson
ulf.hansson at linaro.org
Thu Apr 24 04:52:46 PDT 2014
On 9 April 2014 15:54, Jonas Jensen <jonas.jensen at gmail.com> wrote:
> Add SD/MMC driver for MOXA ART SoCs.
>
> The "MOXA ART MMC controller" is likely a faraday "ftsdc010",
> a controller with support in U-Boot:
>
> http://git.denx.de/?p=u-boot.git;a=blob;f=drivers/mmc/ftsdc010_mci.c
>
> Signed-off-by: Jonas Jensen <jonas.jensen at gmail.com>
Acked-by: Ulf Hansson <ulf.hansson at linaro.org>
> ---
>
> Notes:
> This has been cleaned up further, especially the return value fiasco in v7.
>
> The "MOXA ART MMC controller" is likely a faraday "ftsdc010" (supported by U-Boot [1]).
> Registers are the same albeit named slightly different.
>
> I think this can support other "ftsdc010" SoCs with one reservation for
> power control, which may need hardware specific changes.
>
> Changes since v7:
>
> 1. merge code from U-Boot "ftsdc010" driver [1]
>
> Specifically how it implements status wait, clock divider, fifo width, bus width.
>
> 2. return value cleanup, MMC_ERR_TIMEOUT is no longer used or set at the top of every request
>
> i.e. the default (no error) return value (0) is respected.
>
> Error is only set on actual error, "MMC_ERR_NONE" is no longer used.
>
> 3. FIFO size is now read from register (and respected during PIO transfer)
> 4. supported bus width(s) is now read from register
> 5. !(of_dma_request_slave_channel()) return value check is wrong,
> IS_ERR() must be used.
> 6. clean up and remove unused defines / header includes
> 7. add "faraday,ftsdc010" compatible string (requested by Arnd)
>
> [1] http://git.denx.de/?p=u-boot.git;a=blob;f=drivers/mmc/ftsdc010_mci.c
>
> Applies to next-20140409
>
> .../devicetree/bindings/mmc/moxa,moxart-mmc.txt | 30 +
> drivers/mmc/host/Kconfig | 9 +
> drivers/mmc/host/Makefile | 1 +
> drivers/mmc/host/moxart-mmc.c | 730 +++++++++++++++++++++
> 4 files changed, 770 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt
> create mode 100644 drivers/mmc/host/moxart-mmc.c
>
> diff --git a/Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt b/Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt
> new file mode 100644
> index 0000000..b638191
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mmc/moxa,moxart-mmc.txt
> @@ -0,0 +1,30 @@
> +MOXA ART MMC Host Controller Interface
> +
> + Inherits from mmc binding[1].
> +
> + [1] Documentation/devicetree/bindings/mmc/mmc.txt
> +
> +Required properties:
> +
> +- compatible : Must be "moxa,moxart-mmc" or "faraday,ftsdc010"
> +- reg : Should contain registers location and length
> +- interrupts : Should contain the interrupt number
> +- clocks : Should contain phandle for the clock feeding the MMC controller
> +
> +Optional properties:
> +
> +- dmas : Should contain two DMA channels, line request number must be 5 for
> + both channels
> +- dma-names : Must be "tx", "rx"
> +
> +Example:
> +
> + mmc: mmc at 98e00000 {
> + compatible = "moxa,moxart-mmc";
> + reg = <0x98e00000 0x5C>;
> + interrupts = <5 0>;
> + clocks = <&clk_apb>;
> + dmas = <&dma 5>,
> + <&dma 5>;
> + dma-names = "tx", "rx";
> + };
> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
> index 8aaf8c1..eb93b81 100644
> --- a/drivers/mmc/host/Kconfig
> +++ b/drivers/mmc/host/Kconfig
> @@ -283,6 +283,15 @@ config MMC_SDHCI_BCM2835
>
> If unsure, say N.
>
> +config MMC_MOXART
> + tristate "MOXART SD/MMC Host Controller support"
> + depends on ARCH_MOXART && MMC
> + help
> + This selects support for the MOXART SD/MMC Host Controller.
> + MOXA provides one multi-functional card reader which can
> + be found on some embedded hardware such as UC-7112-LX.
> + If you have a controller with this interface, say Y here.
> +
> config MMC_OMAP
> tristate "TI OMAP Multimedia Card Interface support"
> depends on ARCH_OMAP
> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
> index 0c8aa5e..dfa6ecb 100644
> --- a/drivers/mmc/host/Makefile
> +++ b/drivers/mmc/host/Makefile
> @@ -50,6 +50,7 @@ obj-$(CONFIG_MMC_JZ4740) += jz4740_mmc.o
> 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_REALTEK_PCI) += rtsx_pci_sdmmc.o
>
> diff --git a/drivers/mmc/host/moxart-mmc.c b/drivers/mmc/host/moxart-mmc.c
> new file mode 100644
> index 0000000..74924a0
> --- /dev/null
> +++ b/drivers/mmc/host/moxart-mmc.c
> @@ -0,0 +1,730 @@
> +/*
> + * MOXA ART MMC host driver.
> + *
> + * Copyright (C) 2014 Jonas Jensen
> + *
> + * Jonas Jensen <jonas.jensen at gmail.com>
> + *
> + * Based on code from
> + * Moxa Technologies Co., Ltd. <www.moxa.com>
> + *
> + * This file is licensed under the terms of the GNU General Public
> + * License version 2. This program is licensed "as is" without any
> + * warranty of any kind, whether express or implied.
> + */
> +
> +#include <linux/version.h>
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/platform_device.h>
> +#include <linux/delay.h>
> +#include <linux/interrupt.h>
> +#include <linux/blkdev.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/dmaengine.h>
> +#include <linux/mmc/host.h>
> +#include <linux/mmc/sd.h>
> +#include <linux/sched.h>
> +#include <linux/io.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/clk.h>
> +#include <linux/bitops.h>
> +#include <linux/of_dma.h>
> +#include <linux/spinlock.h>
> +
> +#define REG_COMMAND 0
> +#define REG_ARGUMENT 4
> +#define REG_RESPONSE0 8
> +#define REG_RESPONSE1 12
> +#define REG_RESPONSE2 16
> +#define REG_RESPONSE3 20
> +#define REG_RESPONSE_COMMAND 24
> +#define REG_DATA_CONTROL 28
> +#define REG_DATA_TIMER 32
> +#define REG_DATA_LENGTH 36
> +#define REG_STATUS 40
> +#define REG_CLEAR 44
> +#define REG_INTERRUPT_MASK 48
> +#define REG_POWER_CONTROL 52
> +#define REG_CLOCK_CONTROL 56
> +#define REG_BUS_WIDTH 60
> +#define REG_DATA_WINDOW 64
> +#define REG_FEATURE 68
> +#define REG_REVISION 72
> +
> +/* REG_COMMAND */
> +#define CMD_SDC_RESET BIT(10)
> +#define CMD_EN BIT(9)
> +#define CMD_APP_CMD BIT(8)
> +#define CMD_LONG_RSP BIT(7)
> +#define CMD_NEED_RSP BIT(6)
> +#define CMD_IDX_MASK 0x3f
> +
> +/* REG_RESPONSE_COMMAND */
> +#define RSP_CMD_APP BIT(6)
> +#define RSP_CMD_IDX_MASK 0x3f
> +
> +/* REG_DATA_CONTROL */
> +#define DCR_DATA_FIFO_RESET BIT(8)
> +#define DCR_DATA_THRES BIT(7)
> +#define DCR_DATA_EN BIT(6)
> +#define DCR_DMA_EN BIT(5)
> +#define DCR_DATA_WRITE BIT(4)
> +#define DCR_BLK_SIZE 0x0f
> +
> +/* REG_DATA_LENGTH */
> +#define DATA_LEN_MASK 0xffffff
> +
> +/* REG_STATUS */
> +#define WRITE_PROT BIT(12)
> +#define CARD_DETECT BIT(11)
> +/* 1-10 below can be sent to either registers, interrupt or clear. */
> +#define CARD_CHANGE BIT(10)
> +#define FIFO_ORUN BIT(9)
> +#define FIFO_URUN BIT(8)
> +#define DATA_END BIT(7)
> +#define CMD_SENT BIT(6)
> +#define DATA_CRC_OK BIT(5)
> +#define RSP_CRC_OK BIT(4)
> +#define DATA_TIMEOUT BIT(3)
> +#define RSP_TIMEOUT BIT(2)
> +#define DATA_CRC_FAIL BIT(1)
> +#define RSP_CRC_FAIL BIT(0)
> +
> +#define MASK_RSP (RSP_TIMEOUT | RSP_CRC_FAIL | \
> + RSP_CRC_OK | CARD_DETECT | CMD_SENT)
> +
> +#define MASK_DATA (DATA_CRC_OK | DATA_END | \
> + DATA_CRC_FAIL | DATA_TIMEOUT)
> +
> +#define MASK_INTR_PIO (FIFO_URUN | FIFO_ORUN | CARD_CHANGE)
> +
> +/* REG_POWER_CONTROL */
> +#define SD_POWER_ON BIT(4)
> +#define SD_POWER_MASK 0x0f
> +
> +/* REG_CLOCK_CONTROL */
> +#define CLK_HISPD BIT(9)
> +#define CLK_OFF BIT(8)
> +#define CLK_SD BIT(7)
> +#define CLK_DIV_MASK 0x7f
> +
> +/* REG_BUS_WIDTH */
> +#define BUS_WIDTH_8 BIT(2)
> +#define BUS_WIDTH_4 BIT(1)
> +#define BUS_WIDTH_1 BIT(0)
> +
> +#define MMC_VDD_360 23
> +#define MIN_POWER (MMC_VDD_360 - SD_POWER_MASK)
> +#define MAX_RETRIES 500000
> +
> +struct moxart_host {
> + spinlock_t lock;
> +
> + void __iomem *base;
> +
> + phys_addr_t reg_phys;
> +
> + struct dma_chan *dma_chan_tx;
> + struct dma_chan *dma_chan_rx;
> + struct dma_async_tx_descriptor *tx_desc;
> + struct mmc_host *mmc;
> + struct mmc_request *mrq;
> + struct scatterlist *cur_sg;
> + struct completion dma_complete;
> + struct completion pio_complete;
> +
> + u32 num_sg;
> + u32 data_remain;
> + u32 data_len;
> + u32 fifo_width;
> + u32 timeout;
> + u32 rate;
> +
> + long sysclk;
> +
> + bool have_dma;
> + bool is_removed;
> +};
> +
> +static inline void moxart_init_sg(struct moxart_host *host,
> + struct mmc_data *data)
> +{
> + host->cur_sg = data->sg;
> + host->num_sg = data->sg_len;
> + host->data_remain = host->cur_sg->length;
> +
> + if (host->data_remain > host->data_len)
> + host->data_remain = host->data_len;
> +}
> +
> +static inline int moxart_next_sg(struct moxart_host *host)
> +{
> + int remain;
> + struct mmc_data *data = host->mrq->cmd->data;
> +
> + host->cur_sg++;
> + host->num_sg--;
> +
> + if (host->num_sg > 0) {
> + host->data_remain = host->cur_sg->length;
> + remain = host->data_len - data->bytes_xfered;
> + if (remain > 0 && remain < host->data_remain)
> + host->data_remain = remain;
> + }
> +
> + return host->num_sg;
> +}
> +
> +static int moxart_wait_for_status(struct moxart_host *host,
> + u32 mask, u32 *status)
> +{
> + int ret = -ETIMEDOUT;
> + u32 i;
> +
> + for (i = 0; i < MAX_RETRIES; i++) {
> + *status = readl(host->base + REG_STATUS);
> + if (!(*status & mask)) {
> + udelay(5);
> + continue;
> + }
> + writel(*status & mask, host->base + REG_CLEAR);
> + ret = 0;
> + break;
> + }
> +
> + if (ret)
> + dev_err(mmc_dev(host->mmc), "timed out waiting for status\n");
> +
> + return ret;
> +}
> +
> +
> +static void moxart_send_command(struct moxart_host *host,
> + struct mmc_command *cmd)
> +{
> + u32 status, cmdctrl;
> +
> + writel(RSP_TIMEOUT | RSP_CRC_OK |
> + RSP_CRC_FAIL | CMD_SENT, host->base + REG_CLEAR);
> + writel(cmd->arg, host->base + REG_ARGUMENT);
> +
> + cmdctrl = cmd->opcode & CMD_IDX_MASK;
> + if (cmdctrl == SD_APP_SET_BUS_WIDTH || cmdctrl == SD_APP_OP_COND ||
> + cmdctrl == SD_APP_SEND_SCR || cmdctrl == SD_APP_SD_STATUS ||
> + cmdctrl == SD_APP_SEND_NUM_WR_BLKS)
> + cmdctrl |= CMD_APP_CMD;
> +
> + if (cmd->flags & MMC_RSP_PRESENT)
> + cmdctrl |= CMD_NEED_RSP;
> +
> + if (cmd->flags & MMC_RSP_136)
> + cmdctrl |= CMD_LONG_RSP;
> +
> + writel(cmdctrl | CMD_EN, host->base + REG_COMMAND);
> +
> + if (moxart_wait_for_status(host, MASK_RSP, &status) == -ETIMEDOUT)
> + cmd->error = -ETIMEDOUT;
> +
> + if (status & RSP_TIMEOUT) {
> + cmd->error = -ETIMEDOUT;
> + return;
> + }
> + if (status & RSP_CRC_FAIL) {
> + cmd->error = -EIO;
> + return;
> + }
> + if (status & RSP_CRC_OK) {
> + if (cmd->flags & MMC_RSP_136) {
> + cmd->resp[3] = readl(host->base + REG_RESPONSE0);
> + cmd->resp[2] = readl(host->base + REG_RESPONSE1);
> + cmd->resp[1] = readl(host->base + REG_RESPONSE2);
> + cmd->resp[0] = readl(host->base + REG_RESPONSE3);
> + } else {
> + cmd->resp[0] = readl(host->base + REG_RESPONSE0);
> + }
> + }
> +}
> +
> +static void moxart_dma_complete(void *param)
> +{
> + struct moxart_host *host = param;
> +
> + complete(&host->dma_complete);
> +}
> +
> +static void moxart_transfer_dma(struct mmc_data *data, struct moxart_host *host)
> +{
> + u32 len, dir_data, dir_slave;
> + unsigned long dma_time;
> + struct dma_async_tx_descriptor *desc = NULL;
> + struct dma_chan *dma_chan;
> +
> + if (host->data_len == data->bytes_xfered)
> + return;
> +
> + if (data->flags & MMC_DATA_WRITE) {
> + dma_chan = host->dma_chan_tx;
> + dir_data = DMA_TO_DEVICE;
> + dir_slave = DMA_MEM_TO_DEV;
> + } else {
> + dma_chan = host->dma_chan_rx;
> + dir_data = DMA_FROM_DEVICE;
> + dir_slave = DMA_DEV_TO_MEM;
> + }
> +
> + len = dma_map_sg(dma_chan->device->dev, data->sg,
> + data->sg_len, dir_data);
> +
> + if (len > 0) {
> + desc = dmaengine_prep_slave_sg(dma_chan, data->sg,
> + len, dir_slave,
> + DMA_PREP_INTERRUPT |
> + DMA_CTRL_ACK);
> + } else {
> + dev_err(mmc_dev(host->mmc), "dma_map_sg returned zero length\n");
> + }
> +
> + if (desc) {
> + host->tx_desc = desc;
> + desc->callback = moxart_dma_complete;
> + desc->callback_param = host;
> + dmaengine_submit(desc);
> + dma_async_issue_pending(dma_chan);
> + }
> +
> + data->bytes_xfered += host->data_remain;
> +
> + dma_time = wait_for_completion_interruptible_timeout(
> + &host->dma_complete, host->timeout);
> +
> + dma_unmap_sg(dma_chan->device->dev,
> + data->sg, data->sg_len,
> + dir_data);
> +}
> +
> +
> +static void moxart_transfer_pio(struct moxart_host *host)
> +{
> + struct mmc_data *data = host->mrq->cmd->data;
> + u32 *sgp, len = 0, remain, status;
> +
> + if (host->data_len == data->bytes_xfered)
> + return;
> +
> + sgp = sg_virt(host->cur_sg);
> + remain = host->data_remain;
> +
> + if (data->flags & MMC_DATA_WRITE) {
> + while (remain > 0) {
> + if (moxart_wait_for_status(host, FIFO_URUN, &status)
> + == -ETIMEDOUT) {
> + data->error = -ETIMEDOUT;
> + complete(&host->pio_complete);
> + return;
> + }
> + for (len = 0; len < remain && len < host->fifo_width;) {
> + iowrite32(*sgp, host->base + REG_DATA_WINDOW);
> + sgp++;
> + len += 4;
> + }
> + remain -= len;
> + }
> +
> + } else {
> + while (remain > 0) {
> + if (moxart_wait_for_status(host, FIFO_ORUN, &status)
> + == -ETIMEDOUT) {
> + data->error = -ETIMEDOUT;
> + complete(&host->pio_complete);
> + return;
> + }
> + for (len = 0; len < remain && len < host->fifo_width;) {
> + /* SCR data must be read in big endian. */
> + if (data->mrq->cmd->opcode == SD_APP_SEND_SCR)
> + *sgp = ioread32be(host->base +
> + REG_DATA_WINDOW);
> + else
> + *sgp = ioread32(host->base +
> + REG_DATA_WINDOW);
> + sgp++;
> + len += 4;
> + }
> + remain -= len;
> + }
> + }
> +
> + data->bytes_xfered += host->data_remain - remain;
> + host->data_remain = remain;
> +
> + if (host->data_len != data->bytes_xfered)
> + moxart_next_sg(host);
> + else
> + complete(&host->pio_complete);
> +}
> +
> +static void moxart_prepare_data(struct moxart_host *host)
> +{
> + struct mmc_data *data = host->mrq->cmd->data;
> + u32 datactrl;
> + int blksz_bits;
> +
> + if (!data)
> + return;
> +
> + host->data_len = data->blocks * data->blksz;
> + blksz_bits = ffs(data->blksz) - 1;
> + BUG_ON(1 << blksz_bits != data->blksz);
> +
> + moxart_init_sg(host, data);
> +
> + datactrl = DCR_DATA_EN | (blksz_bits & DCR_BLK_SIZE);
> +
> + if (data->flags & MMC_DATA_WRITE)
> + datactrl |= DCR_DATA_WRITE;
> +
> + if ((host->data_len > host->fifo_width) && host->have_dma)
> + datactrl |= DCR_DMA_EN;
> +
> + writel(DCR_DATA_FIFO_RESET, host->base + REG_DATA_CONTROL);
> + writel(MASK_DATA | FIFO_URUN | FIFO_ORUN, host->base + REG_CLEAR);
> + writel(host->rate, host->base + REG_DATA_TIMER);
> + writel(host->data_len, host->base + REG_DATA_LENGTH);
> + writel(datactrl, host->base + REG_DATA_CONTROL);
> +}
> +
> +static void moxart_request(struct mmc_host *mmc, struct mmc_request *mrq)
> +{
> + struct moxart_host *host = mmc_priv(mmc);
> + unsigned long pio_time, flags;
> + u32 status;
> +
> + spin_lock_irqsave(&host->lock, flags);
> +
> + init_completion(&host->dma_complete);
> + init_completion(&host->pio_complete);
> +
> + host->mrq = mrq;
> +
> + if (readl(host->base + REG_STATUS) & CARD_DETECT) {
> + mrq->cmd->error = -ETIMEDOUT;
> + goto request_done;
> + }
> +
> + moxart_prepare_data(host);
> + moxart_send_command(host, host->mrq->cmd);
> +
> + if (mrq->cmd->data) {
> + if ((host->data_len > host->fifo_width) && host->have_dma) {
> +
> + writel(CARD_CHANGE, host->base + REG_INTERRUPT_MASK);
> +
> + spin_unlock_irqrestore(&host->lock, flags);
> +
> + moxart_transfer_dma(mrq->cmd->data, host);
> +
> + spin_lock_irqsave(&host->lock, flags);
> + } else {
> +
> + writel(MASK_INTR_PIO, host->base + REG_INTERRUPT_MASK);
> +
> + spin_unlock_irqrestore(&host->lock, flags);
> +
> + /* PIO transfers start from interrupt. */
> + pio_time = wait_for_completion_interruptible_timeout(
> + &host->pio_complete, host->timeout);
> +
> + spin_lock_irqsave(&host->lock, flags);
> + }
> +
> + if (host->is_removed) {
> + dev_err(mmc_dev(host->mmc), "card removed\n");
> + mrq->cmd->error = -ETIMEDOUT;
> + goto request_done;
> + }
> +
> + if (moxart_wait_for_status(host, MASK_DATA, &status)
> + == -ETIMEDOUT) {
> + mrq->cmd->data->error = -ETIMEDOUT;
> + goto request_done;
> + }
> +
> + if (status & DATA_CRC_FAIL)
> + mrq->cmd->data->error = -ETIMEDOUT;
> +
> + if (mrq->cmd->data->stop)
> + moxart_send_command(host, mrq->cmd->data->stop);
> + }
> +
> +request_done:
> + spin_unlock_irqrestore(&host->lock, flags);
> + mmc_request_done(host->mmc, mrq);
> +}
> +
> +static irqreturn_t moxart_irq(int irq, void *devid)
> +{
> + struct moxart_host *host = (struct moxart_host *)devid;
> + u32 status;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&host->lock, flags);
> +
> + status = readl(host->base + REG_STATUS);
> + if (status & CARD_CHANGE) {
> + host->is_removed = status & CARD_DETECT;
> + if (host->is_removed && host->have_dma) {
> + dmaengine_terminate_all(host->dma_chan_tx);
> + dmaengine_terminate_all(host->dma_chan_rx);
> + }
> + host->mrq = NULL;
> + writel(MASK_INTR_PIO, host->base + REG_CLEAR);
> + writel(CARD_CHANGE, host->base + REG_INTERRUPT_MASK);
> + mmc_detect_change(host->mmc, 0);
> + }
> + if (status & (FIFO_ORUN | FIFO_URUN) && host->mrq)
> + moxart_transfer_pio(host);
> +
> + spin_unlock_irqrestore(&host->lock, flags);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static void moxart_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> +{
> + struct moxart_host *host = mmc_priv(mmc);
> + unsigned long flags;
> + u8 power, div;
> + u32 ctrl;
> +
> + spin_lock_irqsave(&host->lock, flags);
> +
> + if (ios->clock) {
> + for (div = 0; div < CLK_DIV_MASK; ++div) {
> + if (ios->clock >= host->sysclk / (2 * (div + 1)))
> + break;
> + }
> + ctrl = CLK_SD | div;
> + host->rate = host->sysclk / (2 * (div + 1));
> + if (host->rate > host->sysclk)
> + ctrl |= CLK_HISPD;
> + writel(ctrl, host->base + REG_CLOCK_CONTROL);
> + }
> +
> + if (ios->power_mode == MMC_POWER_OFF) {
> + writel(readl(host->base + REG_POWER_CONTROL) & ~SD_POWER_ON,
> + host->base + REG_POWER_CONTROL);
> + } else {
> + if (ios->vdd < MIN_POWER)
> + power = 0;
> + else
> + power = ios->vdd - MIN_POWER;
> +
> + writel(SD_POWER_ON | (u32) power,
> + host->base + REG_POWER_CONTROL);
> + }
> +
> + switch (ios->bus_width) {
> + case MMC_BUS_WIDTH_4:
> + writel(BUS_WIDTH_4, host->base + REG_BUS_WIDTH);
> + break;
> + case MMC_BUS_WIDTH_8:
> + writel(BUS_WIDTH_8, host->base + REG_BUS_WIDTH);
> + break;
> + default:
> + writel(BUS_WIDTH_1, host->base + REG_BUS_WIDTH);
> + break;
> + }
> +
> + spin_unlock_irqrestore(&host->lock, flags);
> +}
> +
> +
> +static int moxart_get_ro(struct mmc_host *mmc)
> +{
> + struct moxart_host *host = mmc_priv(mmc);
> +
> + return !!(readl(host->base + REG_STATUS) & WRITE_PROT);
> +}
> +
> +static struct mmc_host_ops moxart_ops = {
> + .request = moxart_request,
> + .set_ios = moxart_set_ios,
> + .get_ro = moxart_get_ro,
> +};
> +
> +static int moxart_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct device_node *node = dev->of_node;
> + struct resource res_mmc;
> + struct mmc_host *mmc;
> + struct moxart_host *host = NULL;
> + struct dma_slave_config cfg;
> + struct clk *clk;
> + void __iomem *reg_mmc;
> + dma_cap_mask_t mask;
> + int irq, ret;
> + u32 i;
> +
> + mmc = mmc_alloc_host(sizeof(struct moxart_host), dev);
> + if (!mmc) {
> + dev_err(dev, "mmc_alloc_host failed\n");
> + ret = -ENOMEM;
> + goto out;
> + }
> +
> + ret = of_address_to_resource(node, 0, &res_mmc);
> + if (ret) {
> + dev_err(dev, "of_address_to_resource failed\n");
> + goto out;
> + }
> +
> + irq = irq_of_parse_and_map(node, 0);
> + if (irq <= 0) {
> + dev_err(dev, "irq_of_parse_and_map failed\n");
> + ret = -EINVAL;
> + goto out;
> + }
> +
> + clk = of_clk_get(node, 0);
> + if (IS_ERR(clk)) {
> + dev_err(dev, "of_clk_get failed\n");
> + ret = PTR_ERR(clk);
> + goto out;
> + }
> +
> + reg_mmc = devm_ioremap_resource(dev, &res_mmc);
> + if (IS_ERR(reg_mmc)) {
> + ret = PTR_ERR(reg_mmc);
> + goto out;
> + }
> +
> + mmc_of_parse(mmc);
> +
> + dma_cap_zero(mask);
> + dma_cap_set(DMA_SLAVE, mask);
> +
> + host = mmc_priv(mmc);
> + host->mmc = mmc;
> + host->base = reg_mmc;
> + host->reg_phys = res_mmc.start;
> + host->timeout = msecs_to_jiffies(1000);
> + host->sysclk = clk_get_rate(clk);
> + host->fifo_width = readl(host->base + REG_FEATURE) << 2;
> + host->dma_chan_tx = of_dma_request_slave_channel(node, "tx");
> + host->dma_chan_rx = of_dma_request_slave_channel(node, "rx");
> +
> + spin_lock_init(&host->lock);
> +
> + mmc->ops = &moxart_ops;
> + mmc->f_max = DIV_ROUND_CLOSEST(host->sysclk, 2);
> + mmc->f_min = DIV_ROUND_CLOSEST(host->sysclk, CLK_DIV_MASK * 2);
> + mmc->ocr_avail = 0xffff00; /* Support 2.0v - 3.6v power. */
> +
> + if (IS_ERR(host->dma_chan_tx) || IS_ERR(host->dma_chan_rx)) {
> + dev_dbg(dev, "PIO mode transfer enabled\n");
> + host->have_dma = false;
> + } else {
> + dev_dbg(dev, "DMA channels found (%p,%p)\n",
> + host->dma_chan_tx, host->dma_chan_rx);
> + host->have_dma = true;
> +
> + cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
> + cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
> +
> + cfg.direction = DMA_MEM_TO_DEV;
> + cfg.src_addr = 0;
> + cfg.dst_addr = host->reg_phys + REG_DATA_WINDOW;
> + dmaengine_slave_config(host->dma_chan_tx, &cfg);
> +
> + cfg.direction = DMA_DEV_TO_MEM;
> + cfg.src_addr = host->reg_phys + REG_DATA_WINDOW;
> + cfg.dst_addr = 0;
> + dmaengine_slave_config(host->dma_chan_rx, &cfg);
> + }
> +
> + switch ((readl(host->base + REG_BUS_WIDTH) >> 3) & 3) {
> + case 1:
> + mmc->caps |= MMC_CAP_4_BIT_DATA;
> + break;
> + case 2:
> + mmc->caps |= MMC_CAP_4_BIT_DATA | MMC_CAP_8_BIT_DATA;
> + break;
> + default:
> + break;
> + }
> +
> + writel(0, host->base + REG_INTERRUPT_MASK);
> +
> + writel(CMD_SDC_RESET, host->base + REG_COMMAND);
> + for (i = 0; i < MAX_RETRIES; i++) {
> + if (!(readl(host->base + REG_COMMAND) & CMD_SDC_RESET))
> + break;
> + udelay(5);
> + }
> +
> + ret = devm_request_irq(dev, irq, moxart_irq, 0, "moxart-mmc", host);
> + if (ret)
> + goto out;
> +
> + dev_set_drvdata(dev, mmc);
> + mmc_add_host(mmc);
> +
> + dev_dbg(dev, "IRQ=%d, FIFO is %d bytes\n", irq, host->fifo_width);
> +
> + return 0;
> +
> +out:
> + if (mmc)
> + mmc_free_host(mmc);
> + return ret;
> +}
> +
> +static int moxart_remove(struct platform_device *pdev)
> +{
> + struct mmc_host *mmc = dev_get_drvdata(&pdev->dev);
> + struct moxart_host *host = mmc_priv(mmc);
> +
> + dev_set_drvdata(&pdev->dev, NULL);
> +
> + if (mmc) {
> + if (!IS_ERR(host->dma_chan_tx))
> + dma_release_channel(host->dma_chan_tx);
> + if (!IS_ERR(host->dma_chan_rx))
> + dma_release_channel(host->dma_chan_rx);
> + mmc_remove_host(mmc);
> + mmc_free_host(mmc);
> +
> + writel(0, host->base + REG_INTERRUPT_MASK);
> + writel(0, host->base + REG_POWER_CONTROL);
> + writel(readl(host->base + REG_CLOCK_CONTROL) | CLK_OFF,
> + host->base + REG_CLOCK_CONTROL);
> + }
> +
> + kfree(host);
> +
> + return 0;
> +}
> +
> +static const struct of_device_id moxart_mmc_match[] = {
> + { .compatible = "moxa,moxart-mmc" },
> + { .compatible = "faraday,ftsdc010" },
> + { }
> +};
> +
> +static struct platform_driver moxart_mmc_driver = {
> + .probe = moxart_probe,
> + .remove = moxart_remove,
> + .driver = {
> + .name = "mmc-moxart",
> + .owner = THIS_MODULE,
> + .of_match_table = moxart_mmc_match,
> + },
> +};
> +module_platform_driver(moxart_mmc_driver);
> +
> +MODULE_ALIAS("platform:mmc-moxart");
> +MODULE_DESCRIPTION("MOXA ART MMC driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Jonas Jensen <jonas.jensen at gmail.com>");
> --
> 1.8.2.1
>
More information about the linux-arm-kernel
mailing list