11b66e94eSJonas Jensen /*
21b66e94eSJonas Jensen * MOXA ART MMC host driver.
31b66e94eSJonas Jensen *
41b66e94eSJonas Jensen * Copyright (C) 2014 Jonas Jensen
51b66e94eSJonas Jensen *
61b66e94eSJonas Jensen * Jonas Jensen <jonas.jensen@gmail.com>
71b66e94eSJonas Jensen *
81b66e94eSJonas Jensen * Based on code from
91b66e94eSJonas Jensen * Moxa Technologies Co., Ltd. <www.moxa.com>
101b66e94eSJonas Jensen *
111b66e94eSJonas Jensen * This file is licensed under the terms of the GNU General Public
121b66e94eSJonas Jensen * License version 2. This program is licensed "as is" without any
131b66e94eSJonas Jensen * warranty of any kind, whether express or implied.
141b66e94eSJonas Jensen */
151b66e94eSJonas Jensen
161b66e94eSJonas Jensen #include <linux/module.h>
171b66e94eSJonas Jensen #include <linux/init.h>
181b66e94eSJonas Jensen #include <linux/platform_device.h>
191b66e94eSJonas Jensen #include <linux/delay.h>
203981c516SArnd Bergmann #include <linux/errno.h>
211b66e94eSJonas Jensen #include <linux/interrupt.h>
221b66e94eSJonas Jensen #include <linux/blkdev.h>
231b66e94eSJonas Jensen #include <linux/dma-mapping.h>
241b66e94eSJonas Jensen #include <linux/dmaengine.h>
251b66e94eSJonas Jensen #include <linux/mmc/host.h>
261b66e94eSJonas Jensen #include <linux/mmc/sd.h>
271b66e94eSJonas Jensen #include <linux/sched.h>
281b66e94eSJonas Jensen #include <linux/io.h>
291b66e94eSJonas Jensen #include <linux/of_address.h>
301b66e94eSJonas Jensen #include <linux/of_irq.h>
311b66e94eSJonas Jensen #include <linux/clk.h>
321b66e94eSJonas Jensen #include <linux/bitops.h>
331b66e94eSJonas Jensen #include <linux/of_dma.h>
341b66e94eSJonas Jensen #include <linux/spinlock.h>
351b66e94eSJonas Jensen
361b66e94eSJonas Jensen #define REG_COMMAND 0
371b66e94eSJonas Jensen #define REG_ARGUMENT 4
381b66e94eSJonas Jensen #define REG_RESPONSE0 8
391b66e94eSJonas Jensen #define REG_RESPONSE1 12
401b66e94eSJonas Jensen #define REG_RESPONSE2 16
411b66e94eSJonas Jensen #define REG_RESPONSE3 20
421b66e94eSJonas Jensen #define REG_RESPONSE_COMMAND 24
431b66e94eSJonas Jensen #define REG_DATA_CONTROL 28
441b66e94eSJonas Jensen #define REG_DATA_TIMER 32
451b66e94eSJonas Jensen #define REG_DATA_LENGTH 36
461b66e94eSJonas Jensen #define REG_STATUS 40
471b66e94eSJonas Jensen #define REG_CLEAR 44
481b66e94eSJonas Jensen #define REG_INTERRUPT_MASK 48
491b66e94eSJonas Jensen #define REG_POWER_CONTROL 52
501b66e94eSJonas Jensen #define REG_CLOCK_CONTROL 56
511b66e94eSJonas Jensen #define REG_BUS_WIDTH 60
521b66e94eSJonas Jensen #define REG_DATA_WINDOW 64
531b66e94eSJonas Jensen #define REG_FEATURE 68
541b66e94eSJonas Jensen #define REG_REVISION 72
551b66e94eSJonas Jensen
561b66e94eSJonas Jensen /* REG_COMMAND */
571b66e94eSJonas Jensen #define CMD_SDC_RESET BIT(10)
581b66e94eSJonas Jensen #define CMD_EN BIT(9)
591b66e94eSJonas Jensen #define CMD_APP_CMD BIT(8)
601b66e94eSJonas Jensen #define CMD_LONG_RSP BIT(7)
611b66e94eSJonas Jensen #define CMD_NEED_RSP BIT(6)
621b66e94eSJonas Jensen #define CMD_IDX_MASK 0x3f
631b66e94eSJonas Jensen
641b66e94eSJonas Jensen /* REG_RESPONSE_COMMAND */
651b66e94eSJonas Jensen #define RSP_CMD_APP BIT(6)
661b66e94eSJonas Jensen #define RSP_CMD_IDX_MASK 0x3f
671b66e94eSJonas Jensen
681b66e94eSJonas Jensen /* REG_DATA_CONTROL */
691b66e94eSJonas Jensen #define DCR_DATA_FIFO_RESET BIT(8)
701b66e94eSJonas Jensen #define DCR_DATA_THRES BIT(7)
711b66e94eSJonas Jensen #define DCR_DATA_EN BIT(6)
721b66e94eSJonas Jensen #define DCR_DMA_EN BIT(5)
731b66e94eSJonas Jensen #define DCR_DATA_WRITE BIT(4)
741b66e94eSJonas Jensen #define DCR_BLK_SIZE 0x0f
751b66e94eSJonas Jensen
761b66e94eSJonas Jensen /* REG_DATA_LENGTH */
771b66e94eSJonas Jensen #define DATA_LEN_MASK 0xffffff
781b66e94eSJonas Jensen
791b66e94eSJonas Jensen /* REG_STATUS */
801b66e94eSJonas Jensen #define WRITE_PROT BIT(12)
811b66e94eSJonas Jensen #define CARD_DETECT BIT(11)
821b66e94eSJonas Jensen /* 1-10 below can be sent to either registers, interrupt or clear. */
831b66e94eSJonas Jensen #define CARD_CHANGE BIT(10)
841b66e94eSJonas Jensen #define FIFO_ORUN BIT(9)
851b66e94eSJonas Jensen #define FIFO_URUN BIT(8)
861b66e94eSJonas Jensen #define DATA_END BIT(7)
871b66e94eSJonas Jensen #define CMD_SENT BIT(6)
881b66e94eSJonas Jensen #define DATA_CRC_OK BIT(5)
891b66e94eSJonas Jensen #define RSP_CRC_OK BIT(4)
901b66e94eSJonas Jensen #define DATA_TIMEOUT BIT(3)
911b66e94eSJonas Jensen #define RSP_TIMEOUT BIT(2)
921b66e94eSJonas Jensen #define DATA_CRC_FAIL BIT(1)
931b66e94eSJonas Jensen #define RSP_CRC_FAIL BIT(0)
941b66e94eSJonas Jensen
951b66e94eSJonas Jensen #define MASK_RSP (RSP_TIMEOUT | RSP_CRC_FAIL | \
961b66e94eSJonas Jensen RSP_CRC_OK | CARD_DETECT | CMD_SENT)
971b66e94eSJonas Jensen
981b66e94eSJonas Jensen #define MASK_DATA (DATA_CRC_OK | DATA_END | \
991b66e94eSJonas Jensen DATA_CRC_FAIL | DATA_TIMEOUT)
1001b66e94eSJonas Jensen
1011b66e94eSJonas Jensen #define MASK_INTR_PIO (FIFO_URUN | FIFO_ORUN | CARD_CHANGE)
1021b66e94eSJonas Jensen
1031b66e94eSJonas Jensen /* REG_POWER_CONTROL */
1041b66e94eSJonas Jensen #define SD_POWER_ON BIT(4)
1051b66e94eSJonas Jensen #define SD_POWER_MASK 0x0f
1061b66e94eSJonas Jensen
1071b66e94eSJonas Jensen /* REG_CLOCK_CONTROL */
1081b66e94eSJonas Jensen #define CLK_HISPD BIT(9)
1091b66e94eSJonas Jensen #define CLK_OFF BIT(8)
1101b66e94eSJonas Jensen #define CLK_SD BIT(7)
1111b66e94eSJonas Jensen #define CLK_DIV_MASK 0x7f
1121b66e94eSJonas Jensen
1131b66e94eSJonas Jensen /* REG_BUS_WIDTH */
11435ca91d1SSergei Antonov #define BUS_WIDTH_4_SUPPORT BIT(3)
11535ca91d1SSergei Antonov #define BUS_WIDTH_4 BIT(2)
1161b66e94eSJonas Jensen #define BUS_WIDTH_1 BIT(0)
1171b66e94eSJonas Jensen
1181b66e94eSJonas Jensen #define MMC_VDD_360 23
1191b66e94eSJonas Jensen #define MIN_POWER (MMC_VDD_360 - SD_POWER_MASK)
1201b66e94eSJonas Jensen #define MAX_RETRIES 500000
1211b66e94eSJonas Jensen
1221b66e94eSJonas Jensen struct moxart_host {
1231b66e94eSJonas Jensen spinlock_t lock;
1241b66e94eSJonas Jensen
1251b66e94eSJonas Jensen void __iomem *base;
1261b66e94eSJonas Jensen
1271b66e94eSJonas Jensen phys_addr_t reg_phys;
1281b66e94eSJonas Jensen
1291b66e94eSJonas Jensen struct dma_chan *dma_chan_tx;
1301b66e94eSJonas Jensen struct dma_chan *dma_chan_rx;
1311b66e94eSJonas Jensen struct dma_async_tx_descriptor *tx_desc;
1321b66e94eSJonas Jensen struct mmc_host *mmc;
1331b66e94eSJonas Jensen struct mmc_request *mrq;
1341b66e94eSJonas Jensen struct scatterlist *cur_sg;
1351b66e94eSJonas Jensen struct completion dma_complete;
1361b66e94eSJonas Jensen struct completion pio_complete;
1371b66e94eSJonas Jensen
1381b66e94eSJonas Jensen u32 num_sg;
1391b66e94eSJonas Jensen u32 data_remain;
1401b66e94eSJonas Jensen u32 data_len;
1411b66e94eSJonas Jensen u32 fifo_width;
1421b66e94eSJonas Jensen u32 timeout;
1431b66e94eSJonas Jensen u32 rate;
1441b66e94eSJonas Jensen
1451b66e94eSJonas Jensen long sysclk;
1461b66e94eSJonas Jensen
1471b66e94eSJonas Jensen bool have_dma;
1481b66e94eSJonas Jensen bool is_removed;
1491b66e94eSJonas Jensen };
1501b66e94eSJonas Jensen
moxart_init_sg(struct moxart_host * host,struct mmc_data * data)1511b66e94eSJonas Jensen static inline void moxart_init_sg(struct moxart_host *host,
1521b66e94eSJonas Jensen struct mmc_data *data)
1531b66e94eSJonas Jensen {
1541b66e94eSJonas Jensen host->cur_sg = data->sg;
1551b66e94eSJonas Jensen host->num_sg = data->sg_len;
1561b66e94eSJonas Jensen host->data_remain = host->cur_sg->length;
1571b66e94eSJonas Jensen
1581b66e94eSJonas Jensen if (host->data_remain > host->data_len)
1591b66e94eSJonas Jensen host->data_remain = host->data_len;
1601b66e94eSJonas Jensen }
1611b66e94eSJonas Jensen
moxart_next_sg(struct moxart_host * host)1621b66e94eSJonas Jensen static inline int moxart_next_sg(struct moxart_host *host)
1631b66e94eSJonas Jensen {
1641b66e94eSJonas Jensen int remain;
1651b66e94eSJonas Jensen struct mmc_data *data = host->mrq->cmd->data;
1661b66e94eSJonas Jensen
1671b66e94eSJonas Jensen host->cur_sg++;
1681b66e94eSJonas Jensen host->num_sg--;
1691b66e94eSJonas Jensen
1701b66e94eSJonas Jensen if (host->num_sg > 0) {
1711b66e94eSJonas Jensen host->data_remain = host->cur_sg->length;
1721b66e94eSJonas Jensen remain = host->data_len - data->bytes_xfered;
1731b66e94eSJonas Jensen if (remain > 0 && remain < host->data_remain)
1741b66e94eSJonas Jensen host->data_remain = remain;
1751b66e94eSJonas Jensen }
1761b66e94eSJonas Jensen
1771b66e94eSJonas Jensen return host->num_sg;
1781b66e94eSJonas Jensen }
1791b66e94eSJonas Jensen
moxart_wait_for_status(struct moxart_host * host,u32 mask,u32 * status)1801b66e94eSJonas Jensen static int moxart_wait_for_status(struct moxart_host *host,
1811b66e94eSJonas Jensen u32 mask, u32 *status)
1821b66e94eSJonas Jensen {
1831b66e94eSJonas Jensen int ret = -ETIMEDOUT;
1841b66e94eSJonas Jensen u32 i;
1851b66e94eSJonas Jensen
1861b66e94eSJonas Jensen for (i = 0; i < MAX_RETRIES; i++) {
1871b66e94eSJonas Jensen *status = readl(host->base + REG_STATUS);
1881b66e94eSJonas Jensen if (!(*status & mask)) {
1891b66e94eSJonas Jensen udelay(5);
1901b66e94eSJonas Jensen continue;
1911b66e94eSJonas Jensen }
1921b66e94eSJonas Jensen writel(*status & mask, host->base + REG_CLEAR);
1931b66e94eSJonas Jensen ret = 0;
1941b66e94eSJonas Jensen break;
1951b66e94eSJonas Jensen }
1961b66e94eSJonas Jensen
1971b66e94eSJonas Jensen if (ret)
1981b66e94eSJonas Jensen dev_err(mmc_dev(host->mmc), "timed out waiting for status\n");
1991b66e94eSJonas Jensen
2001b66e94eSJonas Jensen return ret;
2011b66e94eSJonas Jensen }
2021b66e94eSJonas Jensen
2031b66e94eSJonas Jensen
moxart_send_command(struct moxart_host * host,struct mmc_command * cmd)2041b66e94eSJonas Jensen static void moxart_send_command(struct moxart_host *host,
2051b66e94eSJonas Jensen struct mmc_command *cmd)
2061b66e94eSJonas Jensen {
2071b66e94eSJonas Jensen u32 status, cmdctrl;
2081b66e94eSJonas Jensen
2091b66e94eSJonas Jensen writel(RSP_TIMEOUT | RSP_CRC_OK |
2101b66e94eSJonas Jensen RSP_CRC_FAIL | CMD_SENT, host->base + REG_CLEAR);
2111b66e94eSJonas Jensen writel(cmd->arg, host->base + REG_ARGUMENT);
2121b66e94eSJonas Jensen
2131b66e94eSJonas Jensen cmdctrl = cmd->opcode & CMD_IDX_MASK;
2141b66e94eSJonas Jensen if (cmdctrl == SD_APP_SET_BUS_WIDTH || cmdctrl == SD_APP_OP_COND ||
2151b66e94eSJonas Jensen cmdctrl == SD_APP_SEND_SCR || cmdctrl == SD_APP_SD_STATUS ||
2161b66e94eSJonas Jensen cmdctrl == SD_APP_SEND_NUM_WR_BLKS)
2171b66e94eSJonas Jensen cmdctrl |= CMD_APP_CMD;
2181b66e94eSJonas Jensen
2191b66e94eSJonas Jensen if (cmd->flags & MMC_RSP_PRESENT)
2201b66e94eSJonas Jensen cmdctrl |= CMD_NEED_RSP;
2211b66e94eSJonas Jensen
2221b66e94eSJonas Jensen if (cmd->flags & MMC_RSP_136)
2231b66e94eSJonas Jensen cmdctrl |= CMD_LONG_RSP;
2241b66e94eSJonas Jensen
2251b66e94eSJonas Jensen writel(cmdctrl | CMD_EN, host->base + REG_COMMAND);
2261b66e94eSJonas Jensen
2271b66e94eSJonas Jensen if (moxart_wait_for_status(host, MASK_RSP, &status) == -ETIMEDOUT)
2281b66e94eSJonas Jensen cmd->error = -ETIMEDOUT;
2291b66e94eSJonas Jensen
2301b66e94eSJonas Jensen if (status & RSP_TIMEOUT) {
2311b66e94eSJonas Jensen cmd->error = -ETIMEDOUT;
2321b66e94eSJonas Jensen return;
2331b66e94eSJonas Jensen }
2341b66e94eSJonas Jensen if (status & RSP_CRC_FAIL) {
2351b66e94eSJonas Jensen cmd->error = -EIO;
2361b66e94eSJonas Jensen return;
2371b66e94eSJonas Jensen }
2381b66e94eSJonas Jensen if (status & RSP_CRC_OK) {
2391b66e94eSJonas Jensen if (cmd->flags & MMC_RSP_136) {
2401b66e94eSJonas Jensen cmd->resp[3] = readl(host->base + REG_RESPONSE0);
2411b66e94eSJonas Jensen cmd->resp[2] = readl(host->base + REG_RESPONSE1);
2421b66e94eSJonas Jensen cmd->resp[1] = readl(host->base + REG_RESPONSE2);
2431b66e94eSJonas Jensen cmd->resp[0] = readl(host->base + REG_RESPONSE3);
2441b66e94eSJonas Jensen } else {
2451b66e94eSJonas Jensen cmd->resp[0] = readl(host->base + REG_RESPONSE0);
2461b66e94eSJonas Jensen }
2471b66e94eSJonas Jensen }
2481b66e94eSJonas Jensen }
2491b66e94eSJonas Jensen
moxart_dma_complete(void * param)2501b66e94eSJonas Jensen static void moxart_dma_complete(void *param)
2511b66e94eSJonas Jensen {
2521b66e94eSJonas Jensen struct moxart_host *host = param;
2531b66e94eSJonas Jensen
2541b66e94eSJonas Jensen complete(&host->dma_complete);
2551b66e94eSJonas Jensen }
2561b66e94eSJonas Jensen
moxart_transfer_dma(struct mmc_data * data,struct moxart_host * host)2571b66e94eSJonas Jensen static void moxart_transfer_dma(struct mmc_data *data, struct moxart_host *host)
2581b66e94eSJonas Jensen {
259feeef096SHeiner Kallweit u32 len, dir_slave;
2601b66e94eSJonas Jensen struct dma_async_tx_descriptor *desc = NULL;
2611b66e94eSJonas Jensen struct dma_chan *dma_chan;
2621b66e94eSJonas Jensen
2631b66e94eSJonas Jensen if (host->data_len == data->bytes_xfered)
2641b66e94eSJonas Jensen return;
2651b66e94eSJonas Jensen
2661b66e94eSJonas Jensen if (data->flags & MMC_DATA_WRITE) {
2671b66e94eSJonas Jensen dma_chan = host->dma_chan_tx;
2681b66e94eSJonas Jensen dir_slave = DMA_MEM_TO_DEV;
2691b66e94eSJonas Jensen } else {
2701b66e94eSJonas Jensen dma_chan = host->dma_chan_rx;
2711b66e94eSJonas Jensen dir_slave = DMA_DEV_TO_MEM;
2721b66e94eSJonas Jensen }
2731b66e94eSJonas Jensen
2741b66e94eSJonas Jensen len = dma_map_sg(dma_chan->device->dev, data->sg,
275feeef096SHeiner Kallweit data->sg_len, mmc_get_dma_dir(data));
2761b66e94eSJonas Jensen
2771b66e94eSJonas Jensen if (len > 0) {
2781b66e94eSJonas Jensen desc = dmaengine_prep_slave_sg(dma_chan, data->sg,
2791b66e94eSJonas Jensen len, dir_slave,
2801b66e94eSJonas Jensen DMA_PREP_INTERRUPT |
2811b66e94eSJonas Jensen DMA_CTRL_ACK);
2821b66e94eSJonas Jensen } else {
2831b66e94eSJonas Jensen dev_err(mmc_dev(host->mmc), "dma_map_sg returned zero length\n");
2841b66e94eSJonas Jensen }
2851b66e94eSJonas Jensen
2861b66e94eSJonas Jensen if (desc) {
2871b66e94eSJonas Jensen host->tx_desc = desc;
2881b66e94eSJonas Jensen desc->callback = moxart_dma_complete;
2891b66e94eSJonas Jensen desc->callback_param = host;
2901b66e94eSJonas Jensen dmaengine_submit(desc);
2911b66e94eSJonas Jensen dma_async_issue_pending(dma_chan);
2921b66e94eSJonas Jensen }
2931b66e94eSJonas Jensen
2941b66e94eSJonas Jensen data->bytes_xfered += host->data_remain;
2951b66e94eSJonas Jensen
296ae3519b6SYang Li wait_for_completion_interruptible_timeout(&host->dma_complete,
297ae3519b6SYang Li host->timeout);
2981b66e94eSJonas Jensen
2991b66e94eSJonas Jensen dma_unmap_sg(dma_chan->device->dev,
3001b66e94eSJonas Jensen data->sg, data->sg_len,
301feeef096SHeiner Kallweit mmc_get_dma_dir(data));
3021b66e94eSJonas Jensen }
3031b66e94eSJonas Jensen
3041b66e94eSJonas Jensen
moxart_transfer_pio(struct moxart_host * host)3051b66e94eSJonas Jensen static void moxart_transfer_pio(struct moxart_host *host)
3061b66e94eSJonas Jensen {
3071b66e94eSJonas Jensen struct mmc_data *data = host->mrq->cmd->data;
3081b66e94eSJonas Jensen u32 *sgp, len = 0, remain, status;
3091b66e94eSJonas Jensen
3101b66e94eSJonas Jensen if (host->data_len == data->bytes_xfered)
3111b66e94eSJonas Jensen return;
3121b66e94eSJonas Jensen
3131b66e94eSJonas Jensen sgp = sg_virt(host->cur_sg);
3141b66e94eSJonas Jensen remain = host->data_remain;
3151b66e94eSJonas Jensen
3161b66e94eSJonas Jensen if (data->flags & MMC_DATA_WRITE) {
3171b66e94eSJonas Jensen while (remain > 0) {
3181b66e94eSJonas Jensen if (moxart_wait_for_status(host, FIFO_URUN, &status)
3191b66e94eSJonas Jensen == -ETIMEDOUT) {
3201b66e94eSJonas Jensen data->error = -ETIMEDOUT;
3211b66e94eSJonas Jensen complete(&host->pio_complete);
3221b66e94eSJonas Jensen return;
3231b66e94eSJonas Jensen }
3241b66e94eSJonas Jensen for (len = 0; len < remain && len < host->fifo_width;) {
3251b66e94eSJonas Jensen iowrite32(*sgp, host->base + REG_DATA_WINDOW);
3261b66e94eSJonas Jensen sgp++;
3271b66e94eSJonas Jensen len += 4;
3281b66e94eSJonas Jensen }
3291b66e94eSJonas Jensen remain -= len;
3301b66e94eSJonas Jensen }
3311b66e94eSJonas Jensen
3321b66e94eSJonas Jensen } else {
3331b66e94eSJonas Jensen while (remain > 0) {
3341b66e94eSJonas Jensen if (moxart_wait_for_status(host, FIFO_ORUN, &status)
3351b66e94eSJonas Jensen == -ETIMEDOUT) {
3361b66e94eSJonas Jensen data->error = -ETIMEDOUT;
3371b66e94eSJonas Jensen complete(&host->pio_complete);
3381b66e94eSJonas Jensen return;
3391b66e94eSJonas Jensen }
3401b66e94eSJonas Jensen for (len = 0; len < remain && len < host->fifo_width;) {
341d4426322SSergei Antonov *sgp = ioread32(host->base + REG_DATA_WINDOW);
3421b66e94eSJonas Jensen sgp++;
3431b66e94eSJonas Jensen len += 4;
3441b66e94eSJonas Jensen }
3451b66e94eSJonas Jensen remain -= len;
3461b66e94eSJonas Jensen }
3471b66e94eSJonas Jensen }
3481b66e94eSJonas Jensen
3491b66e94eSJonas Jensen data->bytes_xfered += host->data_remain - remain;
3501b66e94eSJonas Jensen host->data_remain = remain;
3511b66e94eSJonas Jensen
3521b66e94eSJonas Jensen if (host->data_len != data->bytes_xfered)
3531b66e94eSJonas Jensen moxart_next_sg(host);
3541b66e94eSJonas Jensen else
3551b66e94eSJonas Jensen complete(&host->pio_complete);
3561b66e94eSJonas Jensen }
3571b66e94eSJonas Jensen
moxart_prepare_data(struct moxart_host * host)3581b66e94eSJonas Jensen static void moxart_prepare_data(struct moxart_host *host)
3591b66e94eSJonas Jensen {
3601b66e94eSJonas Jensen struct mmc_data *data = host->mrq->cmd->data;
3611b66e94eSJonas Jensen u32 datactrl;
3621b66e94eSJonas Jensen int blksz_bits;
3631b66e94eSJonas Jensen
3641b66e94eSJonas Jensen if (!data)
3651b66e94eSJonas Jensen return;
3661b66e94eSJonas Jensen
3671b66e94eSJonas Jensen host->data_len = data->blocks * data->blksz;
3681b66e94eSJonas Jensen blksz_bits = ffs(data->blksz) - 1;
3691b66e94eSJonas Jensen BUG_ON(1 << blksz_bits != data->blksz);
3701b66e94eSJonas Jensen
3711b66e94eSJonas Jensen moxart_init_sg(host, data);
3721b66e94eSJonas Jensen
3731b66e94eSJonas Jensen datactrl = DCR_DATA_EN | (blksz_bits & DCR_BLK_SIZE);
3741b66e94eSJonas Jensen
3751b66e94eSJonas Jensen if (data->flags & MMC_DATA_WRITE)
3761b66e94eSJonas Jensen datactrl |= DCR_DATA_WRITE;
3771b66e94eSJonas Jensen
3781b66e94eSJonas Jensen if ((host->data_len > host->fifo_width) && host->have_dma)
3791b66e94eSJonas Jensen datactrl |= DCR_DMA_EN;
3801b66e94eSJonas Jensen
3811b66e94eSJonas Jensen writel(DCR_DATA_FIFO_RESET, host->base + REG_DATA_CONTROL);
3821b66e94eSJonas Jensen writel(MASK_DATA | FIFO_URUN | FIFO_ORUN, host->base + REG_CLEAR);
3831b66e94eSJonas Jensen writel(host->rate, host->base + REG_DATA_TIMER);
3841b66e94eSJonas Jensen writel(host->data_len, host->base + REG_DATA_LENGTH);
3851b66e94eSJonas Jensen writel(datactrl, host->base + REG_DATA_CONTROL);
3861b66e94eSJonas Jensen }
3871b66e94eSJonas Jensen
moxart_request(struct mmc_host * mmc,struct mmc_request * mrq)3881b66e94eSJonas Jensen static void moxart_request(struct mmc_host *mmc, struct mmc_request *mrq)
3891b66e94eSJonas Jensen {
3901b66e94eSJonas Jensen struct moxart_host *host = mmc_priv(mmc);
39141f469caSNicholas Mc Guire unsigned long flags;
3921b66e94eSJonas Jensen u32 status;
3931b66e94eSJonas Jensen
3941b66e94eSJonas Jensen spin_lock_irqsave(&host->lock, flags);
3951b66e94eSJonas Jensen
3961b66e94eSJonas Jensen init_completion(&host->dma_complete);
3971b66e94eSJonas Jensen init_completion(&host->pio_complete);
3981b66e94eSJonas Jensen
3991b66e94eSJonas Jensen host->mrq = mrq;
4001b66e94eSJonas Jensen
4011b66e94eSJonas Jensen if (readl(host->base + REG_STATUS) & CARD_DETECT) {
4021b66e94eSJonas Jensen mrq->cmd->error = -ETIMEDOUT;
4031b66e94eSJonas Jensen goto request_done;
4041b66e94eSJonas Jensen }
4051b66e94eSJonas Jensen
4061b66e94eSJonas Jensen moxart_prepare_data(host);
4071b66e94eSJonas Jensen moxart_send_command(host, host->mrq->cmd);
4081b66e94eSJonas Jensen
4091b66e94eSJonas Jensen if (mrq->cmd->data) {
4101b66e94eSJonas Jensen if ((host->data_len > host->fifo_width) && host->have_dma) {
4111b66e94eSJonas Jensen
4121b66e94eSJonas Jensen writel(CARD_CHANGE, host->base + REG_INTERRUPT_MASK);
4131b66e94eSJonas Jensen
4141b66e94eSJonas Jensen spin_unlock_irqrestore(&host->lock, flags);
4151b66e94eSJonas Jensen
4161b66e94eSJonas Jensen moxart_transfer_dma(mrq->cmd->data, host);
4171b66e94eSJonas Jensen
4181b66e94eSJonas Jensen spin_lock_irqsave(&host->lock, flags);
4191b66e94eSJonas Jensen } else {
4201b66e94eSJonas Jensen
4211b66e94eSJonas Jensen writel(MASK_INTR_PIO, host->base + REG_INTERRUPT_MASK);
4221b66e94eSJonas Jensen
4231b66e94eSJonas Jensen spin_unlock_irqrestore(&host->lock, flags);
4241b66e94eSJonas Jensen
4251b66e94eSJonas Jensen /* PIO transfers start from interrupt. */
426ae3519b6SYang Li wait_for_completion_interruptible_timeout(&host->pio_complete,
427ae3519b6SYang Li host->timeout);
4281b66e94eSJonas Jensen
4291b66e94eSJonas Jensen spin_lock_irqsave(&host->lock, flags);
4301b66e94eSJonas Jensen }
4311b66e94eSJonas Jensen
4321b66e94eSJonas Jensen if (host->is_removed) {
4331b66e94eSJonas Jensen dev_err(mmc_dev(host->mmc), "card removed\n");
4341b66e94eSJonas Jensen mrq->cmd->error = -ETIMEDOUT;
4351b66e94eSJonas Jensen goto request_done;
4361b66e94eSJonas Jensen }
4371b66e94eSJonas Jensen
4381b66e94eSJonas Jensen if (moxart_wait_for_status(host, MASK_DATA, &status)
4391b66e94eSJonas Jensen == -ETIMEDOUT) {
4401b66e94eSJonas Jensen mrq->cmd->data->error = -ETIMEDOUT;
4411b66e94eSJonas Jensen goto request_done;
4421b66e94eSJonas Jensen }
4431b66e94eSJonas Jensen
4441b66e94eSJonas Jensen if (status & DATA_CRC_FAIL)
4451b66e94eSJonas Jensen mrq->cmd->data->error = -ETIMEDOUT;
4461b66e94eSJonas Jensen
4471b66e94eSJonas Jensen if (mrq->cmd->data->stop)
4481b66e94eSJonas Jensen moxart_send_command(host, mrq->cmd->data->stop);
4491b66e94eSJonas Jensen }
4501b66e94eSJonas Jensen
4511b66e94eSJonas Jensen request_done:
4521b66e94eSJonas Jensen spin_unlock_irqrestore(&host->lock, flags);
4531b66e94eSJonas Jensen mmc_request_done(host->mmc, mrq);
4541b66e94eSJonas Jensen }
4551b66e94eSJonas Jensen
moxart_irq(int irq,void * devid)4561b66e94eSJonas Jensen static irqreturn_t moxart_irq(int irq, void *devid)
4571b66e94eSJonas Jensen {
4581b66e94eSJonas Jensen struct moxart_host *host = (struct moxart_host *)devid;
4591b66e94eSJonas Jensen u32 status;
4601b66e94eSJonas Jensen
461120ae805STian Tao spin_lock(&host->lock);
4621b66e94eSJonas Jensen
4631b66e94eSJonas Jensen status = readl(host->base + REG_STATUS);
4641b66e94eSJonas Jensen if (status & CARD_CHANGE) {
4651b66e94eSJonas Jensen host->is_removed = status & CARD_DETECT;
4661b66e94eSJonas Jensen if (host->is_removed && host->have_dma) {
4671b66e94eSJonas Jensen dmaengine_terminate_all(host->dma_chan_tx);
4681b66e94eSJonas Jensen dmaengine_terminate_all(host->dma_chan_rx);
4691b66e94eSJonas Jensen }
4701b66e94eSJonas Jensen host->mrq = NULL;
4711b66e94eSJonas Jensen writel(MASK_INTR_PIO, host->base + REG_CLEAR);
4721b66e94eSJonas Jensen writel(CARD_CHANGE, host->base + REG_INTERRUPT_MASK);
4731b66e94eSJonas Jensen mmc_detect_change(host->mmc, 0);
4741b66e94eSJonas Jensen }
4751b66e94eSJonas Jensen if (status & (FIFO_ORUN | FIFO_URUN) && host->mrq)
4761b66e94eSJonas Jensen moxart_transfer_pio(host);
4771b66e94eSJonas Jensen
478120ae805STian Tao spin_unlock(&host->lock);
4791b66e94eSJonas Jensen
4801b66e94eSJonas Jensen return IRQ_HANDLED;
4811b66e94eSJonas Jensen }
4821b66e94eSJonas Jensen
moxart_set_ios(struct mmc_host * mmc,struct mmc_ios * ios)4831b66e94eSJonas Jensen static void moxart_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
4841b66e94eSJonas Jensen {
4851b66e94eSJonas Jensen struct moxart_host *host = mmc_priv(mmc);
4861b66e94eSJonas Jensen unsigned long flags;
4871b66e94eSJonas Jensen u8 power, div;
4881b66e94eSJonas Jensen u32 ctrl;
4891b66e94eSJonas Jensen
4901b66e94eSJonas Jensen spin_lock_irqsave(&host->lock, flags);
4911b66e94eSJonas Jensen
4921b66e94eSJonas Jensen if (ios->clock) {
4931b66e94eSJonas Jensen for (div = 0; div < CLK_DIV_MASK; ++div) {
4941b66e94eSJonas Jensen if (ios->clock >= host->sysclk / (2 * (div + 1)))
4951b66e94eSJonas Jensen break;
4961b66e94eSJonas Jensen }
4971b66e94eSJonas Jensen ctrl = CLK_SD | div;
4981b66e94eSJonas Jensen host->rate = host->sysclk / (2 * (div + 1));
4991b66e94eSJonas Jensen if (host->rate > host->sysclk)
5001b66e94eSJonas Jensen ctrl |= CLK_HISPD;
5011b66e94eSJonas Jensen writel(ctrl, host->base + REG_CLOCK_CONTROL);
5021b66e94eSJonas Jensen }
5031b66e94eSJonas Jensen
5041b66e94eSJonas Jensen if (ios->power_mode == MMC_POWER_OFF) {
5051b66e94eSJonas Jensen writel(readl(host->base + REG_POWER_CONTROL) & ~SD_POWER_ON,
5061b66e94eSJonas Jensen host->base + REG_POWER_CONTROL);
5071b66e94eSJonas Jensen } else {
5081b66e94eSJonas Jensen if (ios->vdd < MIN_POWER)
5091b66e94eSJonas Jensen power = 0;
5101b66e94eSJonas Jensen else
5111b66e94eSJonas Jensen power = ios->vdd - MIN_POWER;
5121b66e94eSJonas Jensen
5131b66e94eSJonas Jensen writel(SD_POWER_ON | (u32) power,
5141b66e94eSJonas Jensen host->base + REG_POWER_CONTROL);
5151b66e94eSJonas Jensen }
5161b66e94eSJonas Jensen
5171b66e94eSJonas Jensen switch (ios->bus_width) {
5181b66e94eSJonas Jensen case MMC_BUS_WIDTH_4:
5191b66e94eSJonas Jensen writel(BUS_WIDTH_4, host->base + REG_BUS_WIDTH);
5201b66e94eSJonas Jensen break;
5211b66e94eSJonas Jensen default:
5221b66e94eSJonas Jensen writel(BUS_WIDTH_1, host->base + REG_BUS_WIDTH);
5231b66e94eSJonas Jensen break;
5241b66e94eSJonas Jensen }
5251b66e94eSJonas Jensen
5261b66e94eSJonas Jensen spin_unlock_irqrestore(&host->lock, flags);
5271b66e94eSJonas Jensen }
5281b66e94eSJonas Jensen
5291b66e94eSJonas Jensen
moxart_get_ro(struct mmc_host * mmc)5301b66e94eSJonas Jensen static int moxart_get_ro(struct mmc_host *mmc)
5311b66e94eSJonas Jensen {
5321b66e94eSJonas Jensen struct moxart_host *host = mmc_priv(mmc);
5331b66e94eSJonas Jensen
5341b66e94eSJonas Jensen return !!(readl(host->base + REG_STATUS) & WRITE_PROT);
5351b66e94eSJonas Jensen }
5361b66e94eSJonas Jensen
537bc860c20SJulia Lawall static const struct mmc_host_ops moxart_ops = {
5381b66e94eSJonas Jensen .request = moxart_request,
5391b66e94eSJonas Jensen .set_ios = moxart_set_ios,
5401b66e94eSJonas Jensen .get_ro = moxart_get_ro,
5411b66e94eSJonas Jensen };
5421b66e94eSJonas Jensen
moxart_probe(struct platform_device * pdev)5431b66e94eSJonas Jensen static int moxart_probe(struct platform_device *pdev)
5441b66e94eSJonas Jensen {
5451b66e94eSJonas Jensen struct device *dev = &pdev->dev;
5461b66e94eSJonas Jensen struct device_node *node = dev->of_node;
5471b66e94eSJonas Jensen struct resource res_mmc;
5481b66e94eSJonas Jensen struct mmc_host *mmc;
5491b66e94eSJonas Jensen struct moxart_host *host = NULL;
5501b66e94eSJonas Jensen struct dma_slave_config cfg;
5511b66e94eSJonas Jensen struct clk *clk;
5521b66e94eSJonas Jensen void __iomem *reg_mmc;
5531b66e94eSJonas Jensen int irq, ret;
5541b66e94eSJonas Jensen u32 i;
5551b66e94eSJonas Jensen
5561b66e94eSJonas Jensen mmc = mmc_alloc_host(sizeof(struct moxart_host), dev);
5571b66e94eSJonas Jensen if (!mmc) {
5581b66e94eSJonas Jensen dev_err(dev, "mmc_alloc_host failed\n");
5591b66e94eSJonas Jensen ret = -ENOMEM;
5600eab756fSColin Ian King goto out_mmc;
5611b66e94eSJonas Jensen }
5621b66e94eSJonas Jensen
5631b66e94eSJonas Jensen ret = of_address_to_resource(node, 0, &res_mmc);
5641b66e94eSJonas Jensen if (ret) {
5651b66e94eSJonas Jensen dev_err(dev, "of_address_to_resource failed\n");
5660eab756fSColin Ian King goto out_mmc;
5671b66e94eSJonas Jensen }
5681b66e94eSJonas Jensen
5691b66e94eSJonas Jensen irq = irq_of_parse_and_map(node, 0);
5701b66e94eSJonas Jensen if (irq <= 0) {
5711b66e94eSJonas Jensen dev_err(dev, "irq_of_parse_and_map failed\n");
5721b66e94eSJonas Jensen ret = -EINVAL;
5730eab756fSColin Ian King goto out_mmc;
5741b66e94eSJonas Jensen }
5751b66e94eSJonas Jensen
5763981c516SArnd Bergmann clk = devm_clk_get(dev, NULL);
5771b66e94eSJonas Jensen if (IS_ERR(clk)) {
5781b66e94eSJonas Jensen ret = PTR_ERR(clk);
5790eab756fSColin Ian King goto out_mmc;
5801b66e94eSJonas Jensen }
5811b66e94eSJonas Jensen
5821b66e94eSJonas Jensen reg_mmc = devm_ioremap_resource(dev, &res_mmc);
5831b66e94eSJonas Jensen if (IS_ERR(reg_mmc)) {
5841b66e94eSJonas Jensen ret = PTR_ERR(reg_mmc);
5850eab756fSColin Ian King goto out_mmc;
5861b66e94eSJonas Jensen }
5871b66e94eSJonas Jensen
5886d2b4218SUlf Hansson ret = mmc_of_parse(mmc);
5896d2b4218SUlf Hansson if (ret)
5900eab756fSColin Ian King goto out_mmc;
5911b66e94eSJonas Jensen
5921b66e94eSJonas Jensen host = mmc_priv(mmc);
5931b66e94eSJonas Jensen host->mmc = mmc;
5941b66e94eSJonas Jensen host->base = reg_mmc;
5951b66e94eSJonas Jensen host->reg_phys = res_mmc.start;
5961b66e94eSJonas Jensen host->timeout = msecs_to_jiffies(1000);
5971b66e94eSJonas Jensen host->sysclk = clk_get_rate(clk);
5981b66e94eSJonas Jensen host->fifo_width = readl(host->base + REG_FEATURE) << 2;
599c2a93d75SPeter Ujfalusi host->dma_chan_tx = dma_request_chan(dev, "tx");
600c2a93d75SPeter Ujfalusi host->dma_chan_rx = dma_request_chan(dev, "rx");
6011b66e94eSJonas Jensen
6021b66e94eSJonas Jensen spin_lock_init(&host->lock);
6031b66e94eSJonas Jensen
6041b66e94eSJonas Jensen mmc->ops = &moxart_ops;
6051b66e94eSJonas Jensen mmc->f_max = DIV_ROUND_CLOSEST(host->sysclk, 2);
6061b66e94eSJonas Jensen mmc->f_min = DIV_ROUND_CLOSEST(host->sysclk, CLK_DIV_MASK * 2);
6071b66e94eSJonas Jensen mmc->ocr_avail = 0xffff00; /* Support 2.0v - 3.6v power. */
60816b492ceSSergei Antonov mmc->max_blk_size = 2048; /* Max. block length in REG_DATA_CONTROL */
60916b492ceSSergei Antonov mmc->max_req_size = DATA_LEN_MASK; /* bits 0-23 in REG_DATA_LENGTH */
61016b492ceSSergei Antonov mmc->max_blk_count = mmc->max_req_size / 512;
6111b66e94eSJonas Jensen
6121b66e94eSJonas Jensen if (IS_ERR(host->dma_chan_tx) || IS_ERR(host->dma_chan_rx)) {
6133981c516SArnd Bergmann if (PTR_ERR(host->dma_chan_tx) == -EPROBE_DEFER ||
6143981c516SArnd Bergmann PTR_ERR(host->dma_chan_rx) == -EPROBE_DEFER) {
6153981c516SArnd Bergmann ret = -EPROBE_DEFER;
6163981c516SArnd Bergmann goto out;
6173981c516SArnd Bergmann }
6188105c2abSXin Xiong if (!IS_ERR(host->dma_chan_tx)) {
6198105c2abSXin Xiong dma_release_channel(host->dma_chan_tx);
6208105c2abSXin Xiong host->dma_chan_tx = NULL;
6218105c2abSXin Xiong }
6228105c2abSXin Xiong if (!IS_ERR(host->dma_chan_rx)) {
6238105c2abSXin Xiong dma_release_channel(host->dma_chan_rx);
6248105c2abSXin Xiong host->dma_chan_rx = NULL;
6258105c2abSXin Xiong }
6261b66e94eSJonas Jensen dev_dbg(dev, "PIO mode transfer enabled\n");
6271b66e94eSJonas Jensen host->have_dma = false;
62816b492ceSSergei Antonov
62916b492ceSSergei Antonov mmc->max_seg_size = mmc->max_req_size;
6301b66e94eSJonas Jensen } else {
6311b66e94eSJonas Jensen dev_dbg(dev, "DMA channels found (%p,%p)\n",
6321b66e94eSJonas Jensen host->dma_chan_tx, host->dma_chan_rx);
6331b66e94eSJonas Jensen host->have_dma = true;
6341b66e94eSJonas Jensen
635ee516535STony Lindgren memset(&cfg, 0, sizeof(cfg));
6361b66e94eSJonas Jensen cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
6371b66e94eSJonas Jensen cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
6381b66e94eSJonas Jensen
6391b66e94eSJonas Jensen cfg.direction = DMA_MEM_TO_DEV;
6401b66e94eSJonas Jensen cfg.src_addr = 0;
6411b66e94eSJonas Jensen cfg.dst_addr = host->reg_phys + REG_DATA_WINDOW;
6421b66e94eSJonas Jensen dmaengine_slave_config(host->dma_chan_tx, &cfg);
6431b66e94eSJonas Jensen
6441b66e94eSJonas Jensen cfg.direction = DMA_DEV_TO_MEM;
6451b66e94eSJonas Jensen cfg.src_addr = host->reg_phys + REG_DATA_WINDOW;
6461b66e94eSJonas Jensen cfg.dst_addr = 0;
6471b66e94eSJonas Jensen dmaengine_slave_config(host->dma_chan_rx, &cfg);
64816b492ceSSergei Antonov
64916b492ceSSergei Antonov mmc->max_seg_size = min3(mmc->max_req_size,
65016b492ceSSergei Antonov dma_get_max_seg_size(host->dma_chan_rx->device->dev),
65116b492ceSSergei Antonov dma_get_max_seg_size(host->dma_chan_tx->device->dev));
6521b66e94eSJonas Jensen }
6531b66e94eSJonas Jensen
65435ca91d1SSergei Antonov if (readl(host->base + REG_BUS_WIDTH) & BUS_WIDTH_4_SUPPORT)
6551b66e94eSJonas Jensen mmc->caps |= MMC_CAP_4_BIT_DATA;
6561b66e94eSJonas Jensen
6571b66e94eSJonas Jensen writel(0, host->base + REG_INTERRUPT_MASK);
6581b66e94eSJonas Jensen
6591b66e94eSJonas Jensen writel(CMD_SDC_RESET, host->base + REG_COMMAND);
6601b66e94eSJonas Jensen for (i = 0; i < MAX_RETRIES; i++) {
6611b66e94eSJonas Jensen if (!(readl(host->base + REG_COMMAND) & CMD_SDC_RESET))
6621b66e94eSJonas Jensen break;
6631b66e94eSJonas Jensen udelay(5);
6641b66e94eSJonas Jensen }
6651b66e94eSJonas Jensen
6661b66e94eSJonas Jensen ret = devm_request_irq(dev, irq, moxart_irq, 0, "moxart-mmc", host);
6671b66e94eSJonas Jensen if (ret)
6681b66e94eSJonas Jensen goto out;
6691b66e94eSJonas Jensen
6701b66e94eSJonas Jensen dev_set_drvdata(dev, mmc);
6710ca18d09SYang Yingliang ret = mmc_add_host(mmc);
6720ca18d09SYang Yingliang if (ret)
6730ca18d09SYang Yingliang goto out;
6741b66e94eSJonas Jensen
6751b66e94eSJonas Jensen dev_dbg(dev, "IRQ=%d, FIFO is %d bytes\n", irq, host->fifo_width);
6761b66e94eSJonas Jensen
6771b66e94eSJonas Jensen return 0;
6781b66e94eSJonas Jensen
6791b66e94eSJonas Jensen out:
6808105c2abSXin Xiong if (!IS_ERR_OR_NULL(host->dma_chan_tx))
6818105c2abSXin Xiong dma_release_channel(host->dma_chan_tx);
6828105c2abSXin Xiong if (!IS_ERR_OR_NULL(host->dma_chan_rx))
6838105c2abSXin Xiong dma_release_channel(host->dma_chan_rx);
6840eab756fSColin Ian King out_mmc:
6851b66e94eSJonas Jensen if (mmc)
6861b66e94eSJonas Jensen mmc_free_host(mmc);
6871b66e94eSJonas Jensen return ret;
6881b66e94eSJonas Jensen }
6891b66e94eSJonas Jensen
moxart_remove(struct platform_device * pdev)690*19d38f77SYangtao Li static void moxart_remove(struct platform_device *pdev)
6911b66e94eSJonas Jensen {
6921b66e94eSJonas Jensen struct mmc_host *mmc = dev_get_drvdata(&pdev->dev);
6931b66e94eSJonas Jensen struct moxart_host *host = mmc_priv(mmc);
6941b66e94eSJonas Jensen
6958105c2abSXin Xiong if (!IS_ERR_OR_NULL(host->dma_chan_tx))
6961b66e94eSJonas Jensen dma_release_channel(host->dma_chan_tx);
6978105c2abSXin Xiong if (!IS_ERR_OR_NULL(host->dma_chan_rx))
6981b66e94eSJonas Jensen dma_release_channel(host->dma_chan_rx);
6991b66e94eSJonas Jensen mmc_remove_host(mmc);
7001b66e94eSJonas Jensen
7011b66e94eSJonas Jensen writel(0, host->base + REG_INTERRUPT_MASK);
7021b66e94eSJonas Jensen writel(0, host->base + REG_POWER_CONTROL);
7031b66e94eSJonas Jensen writel(readl(host->base + REG_CLOCK_CONTROL) | CLK_OFF,
7041b66e94eSJonas Jensen host->base + REG_CLOCK_CONTROL);
705bd2db32eSGreg Kroah-Hartman mmc_free_host(mmc);
7061b66e94eSJonas Jensen }
7071b66e94eSJonas Jensen
7081b66e94eSJonas Jensen static const struct of_device_id moxart_mmc_match[] = {
7091b66e94eSJonas Jensen { .compatible = "moxa,moxart-mmc" },
7101b66e94eSJonas Jensen { .compatible = "faraday,ftsdc010" },
7111b66e94eSJonas Jensen { }
7121b66e94eSJonas Jensen };
71377452353SLuis de Bethencourt MODULE_DEVICE_TABLE(of, moxart_mmc_match);
7141b66e94eSJonas Jensen
7151b66e94eSJonas Jensen static struct platform_driver moxart_mmc_driver = {
7161b66e94eSJonas Jensen .probe = moxart_probe,
717*19d38f77SYangtao Li .remove_new = moxart_remove,
7181b66e94eSJonas Jensen .driver = {
7191b66e94eSJonas Jensen .name = "mmc-moxart",
72021b2cec6SDouglas Anderson .probe_type = PROBE_PREFER_ASYNCHRONOUS,
7211b66e94eSJonas Jensen .of_match_table = moxart_mmc_match,
7221b66e94eSJonas Jensen },
7231b66e94eSJonas Jensen };
7241b66e94eSJonas Jensen module_platform_driver(moxart_mmc_driver);
7251b66e94eSJonas Jensen
7261b66e94eSJonas Jensen MODULE_ALIAS("platform:mmc-moxart");
7271b66e94eSJonas Jensen MODULE_DESCRIPTION("MOXA ART MMC driver");
7281b66e94eSJonas Jensen MODULE_LICENSE("GPL v2");
7291b66e94eSJonas Jensen MODULE_AUTHOR("Jonas Jensen <jonas.jensen@gmail.com>");
730