162306a36Sopenharmony_ci/* 262306a36Sopenharmony_ci * MOXA ART MMC host driver. 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * Copyright (C) 2014 Jonas Jensen 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Jonas Jensen <jonas.jensen@gmail.com> 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Based on code from 962306a36Sopenharmony_ci * Moxa Technologies Co., Ltd. <www.moxa.com> 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * This file is licensed under the terms of the GNU General Public 1262306a36Sopenharmony_ci * License version 2. This program is licensed "as is" without any 1362306a36Sopenharmony_ci * warranty of any kind, whether express or implied. 1462306a36Sopenharmony_ci */ 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#include <linux/module.h> 1762306a36Sopenharmony_ci#include <linux/init.h> 1862306a36Sopenharmony_ci#include <linux/platform_device.h> 1962306a36Sopenharmony_ci#include <linux/delay.h> 2062306a36Sopenharmony_ci#include <linux/errno.h> 2162306a36Sopenharmony_ci#include <linux/interrupt.h> 2262306a36Sopenharmony_ci#include <linux/blkdev.h> 2362306a36Sopenharmony_ci#include <linux/dma-mapping.h> 2462306a36Sopenharmony_ci#include <linux/dmaengine.h> 2562306a36Sopenharmony_ci#include <linux/mmc/host.h> 2662306a36Sopenharmony_ci#include <linux/mmc/sd.h> 2762306a36Sopenharmony_ci#include <linux/sched.h> 2862306a36Sopenharmony_ci#include <linux/io.h> 2962306a36Sopenharmony_ci#include <linux/of_address.h> 3062306a36Sopenharmony_ci#include <linux/of_irq.h> 3162306a36Sopenharmony_ci#include <linux/clk.h> 3262306a36Sopenharmony_ci#include <linux/bitops.h> 3362306a36Sopenharmony_ci#include <linux/of_dma.h> 3462306a36Sopenharmony_ci#include <linux/spinlock.h> 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci#define REG_COMMAND 0 3762306a36Sopenharmony_ci#define REG_ARGUMENT 4 3862306a36Sopenharmony_ci#define REG_RESPONSE0 8 3962306a36Sopenharmony_ci#define REG_RESPONSE1 12 4062306a36Sopenharmony_ci#define REG_RESPONSE2 16 4162306a36Sopenharmony_ci#define REG_RESPONSE3 20 4262306a36Sopenharmony_ci#define REG_RESPONSE_COMMAND 24 4362306a36Sopenharmony_ci#define REG_DATA_CONTROL 28 4462306a36Sopenharmony_ci#define REG_DATA_TIMER 32 4562306a36Sopenharmony_ci#define REG_DATA_LENGTH 36 4662306a36Sopenharmony_ci#define REG_STATUS 40 4762306a36Sopenharmony_ci#define REG_CLEAR 44 4862306a36Sopenharmony_ci#define REG_INTERRUPT_MASK 48 4962306a36Sopenharmony_ci#define REG_POWER_CONTROL 52 5062306a36Sopenharmony_ci#define REG_CLOCK_CONTROL 56 5162306a36Sopenharmony_ci#define REG_BUS_WIDTH 60 5262306a36Sopenharmony_ci#define REG_DATA_WINDOW 64 5362306a36Sopenharmony_ci#define REG_FEATURE 68 5462306a36Sopenharmony_ci#define REG_REVISION 72 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci/* REG_COMMAND */ 5762306a36Sopenharmony_ci#define CMD_SDC_RESET BIT(10) 5862306a36Sopenharmony_ci#define CMD_EN BIT(9) 5962306a36Sopenharmony_ci#define CMD_APP_CMD BIT(8) 6062306a36Sopenharmony_ci#define CMD_LONG_RSP BIT(7) 6162306a36Sopenharmony_ci#define CMD_NEED_RSP BIT(6) 6262306a36Sopenharmony_ci#define CMD_IDX_MASK 0x3f 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci/* REG_RESPONSE_COMMAND */ 6562306a36Sopenharmony_ci#define RSP_CMD_APP BIT(6) 6662306a36Sopenharmony_ci#define RSP_CMD_IDX_MASK 0x3f 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci/* REG_DATA_CONTROL */ 6962306a36Sopenharmony_ci#define DCR_DATA_FIFO_RESET BIT(8) 7062306a36Sopenharmony_ci#define DCR_DATA_THRES BIT(7) 7162306a36Sopenharmony_ci#define DCR_DATA_EN BIT(6) 7262306a36Sopenharmony_ci#define DCR_DMA_EN BIT(5) 7362306a36Sopenharmony_ci#define DCR_DATA_WRITE BIT(4) 7462306a36Sopenharmony_ci#define DCR_BLK_SIZE 0x0f 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci/* REG_DATA_LENGTH */ 7762306a36Sopenharmony_ci#define DATA_LEN_MASK 0xffffff 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci/* REG_STATUS */ 8062306a36Sopenharmony_ci#define WRITE_PROT BIT(12) 8162306a36Sopenharmony_ci#define CARD_DETECT BIT(11) 8262306a36Sopenharmony_ci/* 1-10 below can be sent to either registers, interrupt or clear. */ 8362306a36Sopenharmony_ci#define CARD_CHANGE BIT(10) 8462306a36Sopenharmony_ci#define FIFO_ORUN BIT(9) 8562306a36Sopenharmony_ci#define FIFO_URUN BIT(8) 8662306a36Sopenharmony_ci#define DATA_END BIT(7) 8762306a36Sopenharmony_ci#define CMD_SENT BIT(6) 8862306a36Sopenharmony_ci#define DATA_CRC_OK BIT(5) 8962306a36Sopenharmony_ci#define RSP_CRC_OK BIT(4) 9062306a36Sopenharmony_ci#define DATA_TIMEOUT BIT(3) 9162306a36Sopenharmony_ci#define RSP_TIMEOUT BIT(2) 9262306a36Sopenharmony_ci#define DATA_CRC_FAIL BIT(1) 9362306a36Sopenharmony_ci#define RSP_CRC_FAIL BIT(0) 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci#define MASK_RSP (RSP_TIMEOUT | RSP_CRC_FAIL | \ 9662306a36Sopenharmony_ci RSP_CRC_OK | CARD_DETECT | CMD_SENT) 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci#define MASK_DATA (DATA_CRC_OK | DATA_END | \ 9962306a36Sopenharmony_ci DATA_CRC_FAIL | DATA_TIMEOUT) 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci#define MASK_INTR_PIO (FIFO_URUN | FIFO_ORUN | CARD_CHANGE) 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci/* REG_POWER_CONTROL */ 10462306a36Sopenharmony_ci#define SD_POWER_ON BIT(4) 10562306a36Sopenharmony_ci#define SD_POWER_MASK 0x0f 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci/* REG_CLOCK_CONTROL */ 10862306a36Sopenharmony_ci#define CLK_HISPD BIT(9) 10962306a36Sopenharmony_ci#define CLK_OFF BIT(8) 11062306a36Sopenharmony_ci#define CLK_SD BIT(7) 11162306a36Sopenharmony_ci#define CLK_DIV_MASK 0x7f 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci/* REG_BUS_WIDTH */ 11462306a36Sopenharmony_ci#define BUS_WIDTH_4_SUPPORT BIT(3) 11562306a36Sopenharmony_ci#define BUS_WIDTH_4 BIT(2) 11662306a36Sopenharmony_ci#define BUS_WIDTH_1 BIT(0) 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci#define MMC_VDD_360 23 11962306a36Sopenharmony_ci#define MIN_POWER (MMC_VDD_360 - SD_POWER_MASK) 12062306a36Sopenharmony_ci#define MAX_RETRIES 500000 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_cistruct moxart_host { 12362306a36Sopenharmony_ci spinlock_t lock; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci void __iomem *base; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci phys_addr_t reg_phys; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci struct dma_chan *dma_chan_tx; 13062306a36Sopenharmony_ci struct dma_chan *dma_chan_rx; 13162306a36Sopenharmony_ci struct dma_async_tx_descriptor *tx_desc; 13262306a36Sopenharmony_ci struct mmc_host *mmc; 13362306a36Sopenharmony_ci struct mmc_request *mrq; 13462306a36Sopenharmony_ci struct scatterlist *cur_sg; 13562306a36Sopenharmony_ci struct completion dma_complete; 13662306a36Sopenharmony_ci struct completion pio_complete; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci u32 num_sg; 13962306a36Sopenharmony_ci u32 data_remain; 14062306a36Sopenharmony_ci u32 data_len; 14162306a36Sopenharmony_ci u32 fifo_width; 14262306a36Sopenharmony_ci u32 timeout; 14362306a36Sopenharmony_ci u32 rate; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci long sysclk; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci bool have_dma; 14862306a36Sopenharmony_ci bool is_removed; 14962306a36Sopenharmony_ci}; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_cistatic inline void moxart_init_sg(struct moxart_host *host, 15262306a36Sopenharmony_ci struct mmc_data *data) 15362306a36Sopenharmony_ci{ 15462306a36Sopenharmony_ci host->cur_sg = data->sg; 15562306a36Sopenharmony_ci host->num_sg = data->sg_len; 15662306a36Sopenharmony_ci host->data_remain = host->cur_sg->length; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci if (host->data_remain > host->data_len) 15962306a36Sopenharmony_ci host->data_remain = host->data_len; 16062306a36Sopenharmony_ci} 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_cistatic inline int moxart_next_sg(struct moxart_host *host) 16362306a36Sopenharmony_ci{ 16462306a36Sopenharmony_ci int remain; 16562306a36Sopenharmony_ci struct mmc_data *data = host->mrq->cmd->data; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci host->cur_sg++; 16862306a36Sopenharmony_ci host->num_sg--; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci if (host->num_sg > 0) { 17162306a36Sopenharmony_ci host->data_remain = host->cur_sg->length; 17262306a36Sopenharmony_ci remain = host->data_len - data->bytes_xfered; 17362306a36Sopenharmony_ci if (remain > 0 && remain < host->data_remain) 17462306a36Sopenharmony_ci host->data_remain = remain; 17562306a36Sopenharmony_ci } 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci return host->num_sg; 17862306a36Sopenharmony_ci} 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_cistatic int moxart_wait_for_status(struct moxart_host *host, 18162306a36Sopenharmony_ci u32 mask, u32 *status) 18262306a36Sopenharmony_ci{ 18362306a36Sopenharmony_ci int ret = -ETIMEDOUT; 18462306a36Sopenharmony_ci u32 i; 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci for (i = 0; i < MAX_RETRIES; i++) { 18762306a36Sopenharmony_ci *status = readl(host->base + REG_STATUS); 18862306a36Sopenharmony_ci if (!(*status & mask)) { 18962306a36Sopenharmony_ci udelay(5); 19062306a36Sopenharmony_ci continue; 19162306a36Sopenharmony_ci } 19262306a36Sopenharmony_ci writel(*status & mask, host->base + REG_CLEAR); 19362306a36Sopenharmony_ci ret = 0; 19462306a36Sopenharmony_ci break; 19562306a36Sopenharmony_ci } 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci if (ret) 19862306a36Sopenharmony_ci dev_err(mmc_dev(host->mmc), "timed out waiting for status\n"); 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci return ret; 20162306a36Sopenharmony_ci} 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_cistatic void moxart_send_command(struct moxart_host *host, 20562306a36Sopenharmony_ci struct mmc_command *cmd) 20662306a36Sopenharmony_ci{ 20762306a36Sopenharmony_ci u32 status, cmdctrl; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci writel(RSP_TIMEOUT | RSP_CRC_OK | 21062306a36Sopenharmony_ci RSP_CRC_FAIL | CMD_SENT, host->base + REG_CLEAR); 21162306a36Sopenharmony_ci writel(cmd->arg, host->base + REG_ARGUMENT); 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci cmdctrl = cmd->opcode & CMD_IDX_MASK; 21462306a36Sopenharmony_ci if (cmdctrl == SD_APP_SET_BUS_WIDTH || cmdctrl == SD_APP_OP_COND || 21562306a36Sopenharmony_ci cmdctrl == SD_APP_SEND_SCR || cmdctrl == SD_APP_SD_STATUS || 21662306a36Sopenharmony_ci cmdctrl == SD_APP_SEND_NUM_WR_BLKS) 21762306a36Sopenharmony_ci cmdctrl |= CMD_APP_CMD; 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci if (cmd->flags & MMC_RSP_PRESENT) 22062306a36Sopenharmony_ci cmdctrl |= CMD_NEED_RSP; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci if (cmd->flags & MMC_RSP_136) 22362306a36Sopenharmony_ci cmdctrl |= CMD_LONG_RSP; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci writel(cmdctrl | CMD_EN, host->base + REG_COMMAND); 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci if (moxart_wait_for_status(host, MASK_RSP, &status) == -ETIMEDOUT) 22862306a36Sopenharmony_ci cmd->error = -ETIMEDOUT; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci if (status & RSP_TIMEOUT) { 23162306a36Sopenharmony_ci cmd->error = -ETIMEDOUT; 23262306a36Sopenharmony_ci return; 23362306a36Sopenharmony_ci } 23462306a36Sopenharmony_ci if (status & RSP_CRC_FAIL) { 23562306a36Sopenharmony_ci cmd->error = -EIO; 23662306a36Sopenharmony_ci return; 23762306a36Sopenharmony_ci } 23862306a36Sopenharmony_ci if (status & RSP_CRC_OK) { 23962306a36Sopenharmony_ci if (cmd->flags & MMC_RSP_136) { 24062306a36Sopenharmony_ci cmd->resp[3] = readl(host->base + REG_RESPONSE0); 24162306a36Sopenharmony_ci cmd->resp[2] = readl(host->base + REG_RESPONSE1); 24262306a36Sopenharmony_ci cmd->resp[1] = readl(host->base + REG_RESPONSE2); 24362306a36Sopenharmony_ci cmd->resp[0] = readl(host->base + REG_RESPONSE3); 24462306a36Sopenharmony_ci } else { 24562306a36Sopenharmony_ci cmd->resp[0] = readl(host->base + REG_RESPONSE0); 24662306a36Sopenharmony_ci } 24762306a36Sopenharmony_ci } 24862306a36Sopenharmony_ci} 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_cistatic void moxart_dma_complete(void *param) 25162306a36Sopenharmony_ci{ 25262306a36Sopenharmony_ci struct moxart_host *host = param; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci complete(&host->dma_complete); 25562306a36Sopenharmony_ci} 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_cistatic void moxart_transfer_dma(struct mmc_data *data, struct moxart_host *host) 25862306a36Sopenharmony_ci{ 25962306a36Sopenharmony_ci u32 len, dir_slave; 26062306a36Sopenharmony_ci struct dma_async_tx_descriptor *desc = NULL; 26162306a36Sopenharmony_ci struct dma_chan *dma_chan; 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci if (host->data_len == data->bytes_xfered) 26462306a36Sopenharmony_ci return; 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci if (data->flags & MMC_DATA_WRITE) { 26762306a36Sopenharmony_ci dma_chan = host->dma_chan_tx; 26862306a36Sopenharmony_ci dir_slave = DMA_MEM_TO_DEV; 26962306a36Sopenharmony_ci } else { 27062306a36Sopenharmony_ci dma_chan = host->dma_chan_rx; 27162306a36Sopenharmony_ci dir_slave = DMA_DEV_TO_MEM; 27262306a36Sopenharmony_ci } 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci len = dma_map_sg(dma_chan->device->dev, data->sg, 27562306a36Sopenharmony_ci data->sg_len, mmc_get_dma_dir(data)); 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci if (len > 0) { 27862306a36Sopenharmony_ci desc = dmaengine_prep_slave_sg(dma_chan, data->sg, 27962306a36Sopenharmony_ci len, dir_slave, 28062306a36Sopenharmony_ci DMA_PREP_INTERRUPT | 28162306a36Sopenharmony_ci DMA_CTRL_ACK); 28262306a36Sopenharmony_ci } else { 28362306a36Sopenharmony_ci dev_err(mmc_dev(host->mmc), "dma_map_sg returned zero length\n"); 28462306a36Sopenharmony_ci } 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci if (desc) { 28762306a36Sopenharmony_ci host->tx_desc = desc; 28862306a36Sopenharmony_ci desc->callback = moxart_dma_complete; 28962306a36Sopenharmony_ci desc->callback_param = host; 29062306a36Sopenharmony_ci dmaengine_submit(desc); 29162306a36Sopenharmony_ci dma_async_issue_pending(dma_chan); 29262306a36Sopenharmony_ci } 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci data->bytes_xfered += host->data_remain; 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci wait_for_completion_interruptible_timeout(&host->dma_complete, 29762306a36Sopenharmony_ci host->timeout); 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci dma_unmap_sg(dma_chan->device->dev, 30062306a36Sopenharmony_ci data->sg, data->sg_len, 30162306a36Sopenharmony_ci mmc_get_dma_dir(data)); 30262306a36Sopenharmony_ci} 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_cistatic void moxart_transfer_pio(struct moxart_host *host) 30662306a36Sopenharmony_ci{ 30762306a36Sopenharmony_ci struct mmc_data *data = host->mrq->cmd->data; 30862306a36Sopenharmony_ci u32 *sgp, len = 0, remain, status; 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci if (host->data_len == data->bytes_xfered) 31162306a36Sopenharmony_ci return; 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci sgp = sg_virt(host->cur_sg); 31462306a36Sopenharmony_ci remain = host->data_remain; 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci if (data->flags & MMC_DATA_WRITE) { 31762306a36Sopenharmony_ci while (remain > 0) { 31862306a36Sopenharmony_ci if (moxart_wait_for_status(host, FIFO_URUN, &status) 31962306a36Sopenharmony_ci == -ETIMEDOUT) { 32062306a36Sopenharmony_ci data->error = -ETIMEDOUT; 32162306a36Sopenharmony_ci complete(&host->pio_complete); 32262306a36Sopenharmony_ci return; 32362306a36Sopenharmony_ci } 32462306a36Sopenharmony_ci for (len = 0; len < remain && len < host->fifo_width;) { 32562306a36Sopenharmony_ci iowrite32(*sgp, host->base + REG_DATA_WINDOW); 32662306a36Sopenharmony_ci sgp++; 32762306a36Sopenharmony_ci len += 4; 32862306a36Sopenharmony_ci } 32962306a36Sopenharmony_ci remain -= len; 33062306a36Sopenharmony_ci } 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci } else { 33362306a36Sopenharmony_ci while (remain > 0) { 33462306a36Sopenharmony_ci if (moxart_wait_for_status(host, FIFO_ORUN, &status) 33562306a36Sopenharmony_ci == -ETIMEDOUT) { 33662306a36Sopenharmony_ci data->error = -ETIMEDOUT; 33762306a36Sopenharmony_ci complete(&host->pio_complete); 33862306a36Sopenharmony_ci return; 33962306a36Sopenharmony_ci } 34062306a36Sopenharmony_ci for (len = 0; len < remain && len < host->fifo_width;) { 34162306a36Sopenharmony_ci *sgp = ioread32(host->base + REG_DATA_WINDOW); 34262306a36Sopenharmony_ci sgp++; 34362306a36Sopenharmony_ci len += 4; 34462306a36Sopenharmony_ci } 34562306a36Sopenharmony_ci remain -= len; 34662306a36Sopenharmony_ci } 34762306a36Sopenharmony_ci } 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ci data->bytes_xfered += host->data_remain - remain; 35062306a36Sopenharmony_ci host->data_remain = remain; 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci if (host->data_len != data->bytes_xfered) 35362306a36Sopenharmony_ci moxart_next_sg(host); 35462306a36Sopenharmony_ci else 35562306a36Sopenharmony_ci complete(&host->pio_complete); 35662306a36Sopenharmony_ci} 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_cistatic void moxart_prepare_data(struct moxart_host *host) 35962306a36Sopenharmony_ci{ 36062306a36Sopenharmony_ci struct mmc_data *data = host->mrq->cmd->data; 36162306a36Sopenharmony_ci u32 datactrl; 36262306a36Sopenharmony_ci int blksz_bits; 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci if (!data) 36562306a36Sopenharmony_ci return; 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci host->data_len = data->blocks * data->blksz; 36862306a36Sopenharmony_ci blksz_bits = ffs(data->blksz) - 1; 36962306a36Sopenharmony_ci BUG_ON(1 << blksz_bits != data->blksz); 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci moxart_init_sg(host, data); 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci datactrl = DCR_DATA_EN | (blksz_bits & DCR_BLK_SIZE); 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci if (data->flags & MMC_DATA_WRITE) 37662306a36Sopenharmony_ci datactrl |= DCR_DATA_WRITE; 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci if ((host->data_len > host->fifo_width) && host->have_dma) 37962306a36Sopenharmony_ci datactrl |= DCR_DMA_EN; 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci writel(DCR_DATA_FIFO_RESET, host->base + REG_DATA_CONTROL); 38262306a36Sopenharmony_ci writel(MASK_DATA | FIFO_URUN | FIFO_ORUN, host->base + REG_CLEAR); 38362306a36Sopenharmony_ci writel(host->rate, host->base + REG_DATA_TIMER); 38462306a36Sopenharmony_ci writel(host->data_len, host->base + REG_DATA_LENGTH); 38562306a36Sopenharmony_ci writel(datactrl, host->base + REG_DATA_CONTROL); 38662306a36Sopenharmony_ci} 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_cistatic void moxart_request(struct mmc_host *mmc, struct mmc_request *mrq) 38962306a36Sopenharmony_ci{ 39062306a36Sopenharmony_ci struct moxart_host *host = mmc_priv(mmc); 39162306a36Sopenharmony_ci unsigned long flags; 39262306a36Sopenharmony_ci u32 status; 39362306a36Sopenharmony_ci 39462306a36Sopenharmony_ci spin_lock_irqsave(&host->lock, flags); 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_ci init_completion(&host->dma_complete); 39762306a36Sopenharmony_ci init_completion(&host->pio_complete); 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_ci host->mrq = mrq; 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_ci if (readl(host->base + REG_STATUS) & CARD_DETECT) { 40262306a36Sopenharmony_ci mrq->cmd->error = -ETIMEDOUT; 40362306a36Sopenharmony_ci goto request_done; 40462306a36Sopenharmony_ci } 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_ci moxart_prepare_data(host); 40762306a36Sopenharmony_ci moxart_send_command(host, host->mrq->cmd); 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_ci if (mrq->cmd->data) { 41062306a36Sopenharmony_ci if ((host->data_len > host->fifo_width) && host->have_dma) { 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci writel(CARD_CHANGE, host->base + REG_INTERRUPT_MASK); 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci spin_unlock_irqrestore(&host->lock, flags); 41562306a36Sopenharmony_ci 41662306a36Sopenharmony_ci moxart_transfer_dma(mrq->cmd->data, host); 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci spin_lock_irqsave(&host->lock, flags); 41962306a36Sopenharmony_ci } else { 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci writel(MASK_INTR_PIO, host->base + REG_INTERRUPT_MASK); 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci spin_unlock_irqrestore(&host->lock, flags); 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci /* PIO transfers start from interrupt. */ 42662306a36Sopenharmony_ci wait_for_completion_interruptible_timeout(&host->pio_complete, 42762306a36Sopenharmony_ci host->timeout); 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_ci spin_lock_irqsave(&host->lock, flags); 43062306a36Sopenharmony_ci } 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci if (host->is_removed) { 43362306a36Sopenharmony_ci dev_err(mmc_dev(host->mmc), "card removed\n"); 43462306a36Sopenharmony_ci mrq->cmd->error = -ETIMEDOUT; 43562306a36Sopenharmony_ci goto request_done; 43662306a36Sopenharmony_ci } 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci if (moxart_wait_for_status(host, MASK_DATA, &status) 43962306a36Sopenharmony_ci == -ETIMEDOUT) { 44062306a36Sopenharmony_ci mrq->cmd->data->error = -ETIMEDOUT; 44162306a36Sopenharmony_ci goto request_done; 44262306a36Sopenharmony_ci } 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_ci if (status & DATA_CRC_FAIL) 44562306a36Sopenharmony_ci mrq->cmd->data->error = -ETIMEDOUT; 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_ci if (mrq->cmd->data->stop) 44862306a36Sopenharmony_ci moxart_send_command(host, mrq->cmd->data->stop); 44962306a36Sopenharmony_ci } 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_cirequest_done: 45262306a36Sopenharmony_ci spin_unlock_irqrestore(&host->lock, flags); 45362306a36Sopenharmony_ci mmc_request_done(host->mmc, mrq); 45462306a36Sopenharmony_ci} 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_cistatic irqreturn_t moxart_irq(int irq, void *devid) 45762306a36Sopenharmony_ci{ 45862306a36Sopenharmony_ci struct moxart_host *host = (struct moxart_host *)devid; 45962306a36Sopenharmony_ci u32 status; 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_ci spin_lock(&host->lock); 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_ci status = readl(host->base + REG_STATUS); 46462306a36Sopenharmony_ci if (status & CARD_CHANGE) { 46562306a36Sopenharmony_ci host->is_removed = status & CARD_DETECT; 46662306a36Sopenharmony_ci if (host->is_removed && host->have_dma) { 46762306a36Sopenharmony_ci dmaengine_terminate_all(host->dma_chan_tx); 46862306a36Sopenharmony_ci dmaengine_terminate_all(host->dma_chan_rx); 46962306a36Sopenharmony_ci } 47062306a36Sopenharmony_ci host->mrq = NULL; 47162306a36Sopenharmony_ci writel(MASK_INTR_PIO, host->base + REG_CLEAR); 47262306a36Sopenharmony_ci writel(CARD_CHANGE, host->base + REG_INTERRUPT_MASK); 47362306a36Sopenharmony_ci mmc_detect_change(host->mmc, 0); 47462306a36Sopenharmony_ci } 47562306a36Sopenharmony_ci if (status & (FIFO_ORUN | FIFO_URUN) && host->mrq) 47662306a36Sopenharmony_ci moxart_transfer_pio(host); 47762306a36Sopenharmony_ci 47862306a36Sopenharmony_ci spin_unlock(&host->lock); 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci return IRQ_HANDLED; 48162306a36Sopenharmony_ci} 48262306a36Sopenharmony_ci 48362306a36Sopenharmony_cistatic void moxart_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) 48462306a36Sopenharmony_ci{ 48562306a36Sopenharmony_ci struct moxart_host *host = mmc_priv(mmc); 48662306a36Sopenharmony_ci unsigned long flags; 48762306a36Sopenharmony_ci u8 power, div; 48862306a36Sopenharmony_ci u32 ctrl; 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_ci spin_lock_irqsave(&host->lock, flags); 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci if (ios->clock) { 49362306a36Sopenharmony_ci for (div = 0; div < CLK_DIV_MASK; ++div) { 49462306a36Sopenharmony_ci if (ios->clock >= host->sysclk / (2 * (div + 1))) 49562306a36Sopenharmony_ci break; 49662306a36Sopenharmony_ci } 49762306a36Sopenharmony_ci ctrl = CLK_SD | div; 49862306a36Sopenharmony_ci host->rate = host->sysclk / (2 * (div + 1)); 49962306a36Sopenharmony_ci if (host->rate > host->sysclk) 50062306a36Sopenharmony_ci ctrl |= CLK_HISPD; 50162306a36Sopenharmony_ci writel(ctrl, host->base + REG_CLOCK_CONTROL); 50262306a36Sopenharmony_ci } 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_ci if (ios->power_mode == MMC_POWER_OFF) { 50562306a36Sopenharmony_ci writel(readl(host->base + REG_POWER_CONTROL) & ~SD_POWER_ON, 50662306a36Sopenharmony_ci host->base + REG_POWER_CONTROL); 50762306a36Sopenharmony_ci } else { 50862306a36Sopenharmony_ci if (ios->vdd < MIN_POWER) 50962306a36Sopenharmony_ci power = 0; 51062306a36Sopenharmony_ci else 51162306a36Sopenharmony_ci power = ios->vdd - MIN_POWER; 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci writel(SD_POWER_ON | (u32) power, 51462306a36Sopenharmony_ci host->base + REG_POWER_CONTROL); 51562306a36Sopenharmony_ci } 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci switch (ios->bus_width) { 51862306a36Sopenharmony_ci case MMC_BUS_WIDTH_4: 51962306a36Sopenharmony_ci writel(BUS_WIDTH_4, host->base + REG_BUS_WIDTH); 52062306a36Sopenharmony_ci break; 52162306a36Sopenharmony_ci default: 52262306a36Sopenharmony_ci writel(BUS_WIDTH_1, host->base + REG_BUS_WIDTH); 52362306a36Sopenharmony_ci break; 52462306a36Sopenharmony_ci } 52562306a36Sopenharmony_ci 52662306a36Sopenharmony_ci spin_unlock_irqrestore(&host->lock, flags); 52762306a36Sopenharmony_ci} 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_cistatic int moxart_get_ro(struct mmc_host *mmc) 53162306a36Sopenharmony_ci{ 53262306a36Sopenharmony_ci struct moxart_host *host = mmc_priv(mmc); 53362306a36Sopenharmony_ci 53462306a36Sopenharmony_ci return !!(readl(host->base + REG_STATUS) & WRITE_PROT); 53562306a36Sopenharmony_ci} 53662306a36Sopenharmony_ci 53762306a36Sopenharmony_cistatic const struct mmc_host_ops moxart_ops = { 53862306a36Sopenharmony_ci .request = moxart_request, 53962306a36Sopenharmony_ci .set_ios = moxart_set_ios, 54062306a36Sopenharmony_ci .get_ro = moxart_get_ro, 54162306a36Sopenharmony_ci}; 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_cistatic int moxart_probe(struct platform_device *pdev) 54462306a36Sopenharmony_ci{ 54562306a36Sopenharmony_ci struct device *dev = &pdev->dev; 54662306a36Sopenharmony_ci struct device_node *node = dev->of_node; 54762306a36Sopenharmony_ci struct resource res_mmc; 54862306a36Sopenharmony_ci struct mmc_host *mmc; 54962306a36Sopenharmony_ci struct moxart_host *host = NULL; 55062306a36Sopenharmony_ci struct dma_slave_config cfg; 55162306a36Sopenharmony_ci struct clk *clk; 55262306a36Sopenharmony_ci void __iomem *reg_mmc; 55362306a36Sopenharmony_ci int irq, ret; 55462306a36Sopenharmony_ci u32 i; 55562306a36Sopenharmony_ci 55662306a36Sopenharmony_ci mmc = mmc_alloc_host(sizeof(struct moxart_host), dev); 55762306a36Sopenharmony_ci if (!mmc) { 55862306a36Sopenharmony_ci dev_err(dev, "mmc_alloc_host failed\n"); 55962306a36Sopenharmony_ci ret = -ENOMEM; 56062306a36Sopenharmony_ci goto out_mmc; 56162306a36Sopenharmony_ci } 56262306a36Sopenharmony_ci 56362306a36Sopenharmony_ci ret = of_address_to_resource(node, 0, &res_mmc); 56462306a36Sopenharmony_ci if (ret) { 56562306a36Sopenharmony_ci dev_err(dev, "of_address_to_resource failed\n"); 56662306a36Sopenharmony_ci goto out_mmc; 56762306a36Sopenharmony_ci } 56862306a36Sopenharmony_ci 56962306a36Sopenharmony_ci irq = irq_of_parse_and_map(node, 0); 57062306a36Sopenharmony_ci if (irq <= 0) { 57162306a36Sopenharmony_ci dev_err(dev, "irq_of_parse_and_map failed\n"); 57262306a36Sopenharmony_ci ret = -EINVAL; 57362306a36Sopenharmony_ci goto out_mmc; 57462306a36Sopenharmony_ci } 57562306a36Sopenharmony_ci 57662306a36Sopenharmony_ci clk = devm_clk_get(dev, NULL); 57762306a36Sopenharmony_ci if (IS_ERR(clk)) { 57862306a36Sopenharmony_ci ret = PTR_ERR(clk); 57962306a36Sopenharmony_ci goto out_mmc; 58062306a36Sopenharmony_ci } 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_ci reg_mmc = devm_ioremap_resource(dev, &res_mmc); 58362306a36Sopenharmony_ci if (IS_ERR(reg_mmc)) { 58462306a36Sopenharmony_ci ret = PTR_ERR(reg_mmc); 58562306a36Sopenharmony_ci goto out_mmc; 58662306a36Sopenharmony_ci } 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_ci ret = mmc_of_parse(mmc); 58962306a36Sopenharmony_ci if (ret) 59062306a36Sopenharmony_ci goto out_mmc; 59162306a36Sopenharmony_ci 59262306a36Sopenharmony_ci host = mmc_priv(mmc); 59362306a36Sopenharmony_ci host->mmc = mmc; 59462306a36Sopenharmony_ci host->base = reg_mmc; 59562306a36Sopenharmony_ci host->reg_phys = res_mmc.start; 59662306a36Sopenharmony_ci host->timeout = msecs_to_jiffies(1000); 59762306a36Sopenharmony_ci host->sysclk = clk_get_rate(clk); 59862306a36Sopenharmony_ci host->fifo_width = readl(host->base + REG_FEATURE) << 2; 59962306a36Sopenharmony_ci host->dma_chan_tx = dma_request_chan(dev, "tx"); 60062306a36Sopenharmony_ci host->dma_chan_rx = dma_request_chan(dev, "rx"); 60162306a36Sopenharmony_ci 60262306a36Sopenharmony_ci spin_lock_init(&host->lock); 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_ci mmc->ops = &moxart_ops; 60562306a36Sopenharmony_ci mmc->f_max = DIV_ROUND_CLOSEST(host->sysclk, 2); 60662306a36Sopenharmony_ci mmc->f_min = DIV_ROUND_CLOSEST(host->sysclk, CLK_DIV_MASK * 2); 60762306a36Sopenharmony_ci mmc->ocr_avail = 0xffff00; /* Support 2.0v - 3.6v power. */ 60862306a36Sopenharmony_ci mmc->max_blk_size = 2048; /* Max. block length in REG_DATA_CONTROL */ 60962306a36Sopenharmony_ci mmc->max_req_size = DATA_LEN_MASK; /* bits 0-23 in REG_DATA_LENGTH */ 61062306a36Sopenharmony_ci mmc->max_blk_count = mmc->max_req_size / 512; 61162306a36Sopenharmony_ci 61262306a36Sopenharmony_ci if (IS_ERR(host->dma_chan_tx) || IS_ERR(host->dma_chan_rx)) { 61362306a36Sopenharmony_ci if (PTR_ERR(host->dma_chan_tx) == -EPROBE_DEFER || 61462306a36Sopenharmony_ci PTR_ERR(host->dma_chan_rx) == -EPROBE_DEFER) { 61562306a36Sopenharmony_ci ret = -EPROBE_DEFER; 61662306a36Sopenharmony_ci goto out; 61762306a36Sopenharmony_ci } 61862306a36Sopenharmony_ci if (!IS_ERR(host->dma_chan_tx)) { 61962306a36Sopenharmony_ci dma_release_channel(host->dma_chan_tx); 62062306a36Sopenharmony_ci host->dma_chan_tx = NULL; 62162306a36Sopenharmony_ci } 62262306a36Sopenharmony_ci if (!IS_ERR(host->dma_chan_rx)) { 62362306a36Sopenharmony_ci dma_release_channel(host->dma_chan_rx); 62462306a36Sopenharmony_ci host->dma_chan_rx = NULL; 62562306a36Sopenharmony_ci } 62662306a36Sopenharmony_ci dev_dbg(dev, "PIO mode transfer enabled\n"); 62762306a36Sopenharmony_ci host->have_dma = false; 62862306a36Sopenharmony_ci 62962306a36Sopenharmony_ci mmc->max_seg_size = mmc->max_req_size; 63062306a36Sopenharmony_ci } else { 63162306a36Sopenharmony_ci dev_dbg(dev, "DMA channels found (%p,%p)\n", 63262306a36Sopenharmony_ci host->dma_chan_tx, host->dma_chan_rx); 63362306a36Sopenharmony_ci host->have_dma = true; 63462306a36Sopenharmony_ci 63562306a36Sopenharmony_ci memset(&cfg, 0, sizeof(cfg)); 63662306a36Sopenharmony_ci cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; 63762306a36Sopenharmony_ci cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; 63862306a36Sopenharmony_ci 63962306a36Sopenharmony_ci cfg.direction = DMA_MEM_TO_DEV; 64062306a36Sopenharmony_ci cfg.src_addr = 0; 64162306a36Sopenharmony_ci cfg.dst_addr = host->reg_phys + REG_DATA_WINDOW; 64262306a36Sopenharmony_ci dmaengine_slave_config(host->dma_chan_tx, &cfg); 64362306a36Sopenharmony_ci 64462306a36Sopenharmony_ci cfg.direction = DMA_DEV_TO_MEM; 64562306a36Sopenharmony_ci cfg.src_addr = host->reg_phys + REG_DATA_WINDOW; 64662306a36Sopenharmony_ci cfg.dst_addr = 0; 64762306a36Sopenharmony_ci dmaengine_slave_config(host->dma_chan_rx, &cfg); 64862306a36Sopenharmony_ci 64962306a36Sopenharmony_ci mmc->max_seg_size = min3(mmc->max_req_size, 65062306a36Sopenharmony_ci dma_get_max_seg_size(host->dma_chan_rx->device->dev), 65162306a36Sopenharmony_ci dma_get_max_seg_size(host->dma_chan_tx->device->dev)); 65262306a36Sopenharmony_ci } 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci if (readl(host->base + REG_BUS_WIDTH) & BUS_WIDTH_4_SUPPORT) 65562306a36Sopenharmony_ci mmc->caps |= MMC_CAP_4_BIT_DATA; 65662306a36Sopenharmony_ci 65762306a36Sopenharmony_ci writel(0, host->base + REG_INTERRUPT_MASK); 65862306a36Sopenharmony_ci 65962306a36Sopenharmony_ci writel(CMD_SDC_RESET, host->base + REG_COMMAND); 66062306a36Sopenharmony_ci for (i = 0; i < MAX_RETRIES; i++) { 66162306a36Sopenharmony_ci if (!(readl(host->base + REG_COMMAND) & CMD_SDC_RESET)) 66262306a36Sopenharmony_ci break; 66362306a36Sopenharmony_ci udelay(5); 66462306a36Sopenharmony_ci } 66562306a36Sopenharmony_ci 66662306a36Sopenharmony_ci ret = devm_request_irq(dev, irq, moxart_irq, 0, "moxart-mmc", host); 66762306a36Sopenharmony_ci if (ret) 66862306a36Sopenharmony_ci goto out; 66962306a36Sopenharmony_ci 67062306a36Sopenharmony_ci dev_set_drvdata(dev, mmc); 67162306a36Sopenharmony_ci ret = mmc_add_host(mmc); 67262306a36Sopenharmony_ci if (ret) 67362306a36Sopenharmony_ci goto out; 67462306a36Sopenharmony_ci 67562306a36Sopenharmony_ci dev_dbg(dev, "IRQ=%d, FIFO is %d bytes\n", irq, host->fifo_width); 67662306a36Sopenharmony_ci 67762306a36Sopenharmony_ci return 0; 67862306a36Sopenharmony_ci 67962306a36Sopenharmony_ciout: 68062306a36Sopenharmony_ci if (!IS_ERR_OR_NULL(host->dma_chan_tx)) 68162306a36Sopenharmony_ci dma_release_channel(host->dma_chan_tx); 68262306a36Sopenharmony_ci if (!IS_ERR_OR_NULL(host->dma_chan_rx)) 68362306a36Sopenharmony_ci dma_release_channel(host->dma_chan_rx); 68462306a36Sopenharmony_ciout_mmc: 68562306a36Sopenharmony_ci if (mmc) 68662306a36Sopenharmony_ci mmc_free_host(mmc); 68762306a36Sopenharmony_ci return ret; 68862306a36Sopenharmony_ci} 68962306a36Sopenharmony_ci 69062306a36Sopenharmony_cistatic void moxart_remove(struct platform_device *pdev) 69162306a36Sopenharmony_ci{ 69262306a36Sopenharmony_ci struct mmc_host *mmc = dev_get_drvdata(&pdev->dev); 69362306a36Sopenharmony_ci struct moxart_host *host = mmc_priv(mmc); 69462306a36Sopenharmony_ci 69562306a36Sopenharmony_ci if (!IS_ERR_OR_NULL(host->dma_chan_tx)) 69662306a36Sopenharmony_ci dma_release_channel(host->dma_chan_tx); 69762306a36Sopenharmony_ci if (!IS_ERR_OR_NULL(host->dma_chan_rx)) 69862306a36Sopenharmony_ci dma_release_channel(host->dma_chan_rx); 69962306a36Sopenharmony_ci mmc_remove_host(mmc); 70062306a36Sopenharmony_ci 70162306a36Sopenharmony_ci writel(0, host->base + REG_INTERRUPT_MASK); 70262306a36Sopenharmony_ci writel(0, host->base + REG_POWER_CONTROL); 70362306a36Sopenharmony_ci writel(readl(host->base + REG_CLOCK_CONTROL) | CLK_OFF, 70462306a36Sopenharmony_ci host->base + REG_CLOCK_CONTROL); 70562306a36Sopenharmony_ci mmc_free_host(mmc); 70662306a36Sopenharmony_ci} 70762306a36Sopenharmony_ci 70862306a36Sopenharmony_cistatic const struct of_device_id moxart_mmc_match[] = { 70962306a36Sopenharmony_ci { .compatible = "moxa,moxart-mmc" }, 71062306a36Sopenharmony_ci { .compatible = "faraday,ftsdc010" }, 71162306a36Sopenharmony_ci { } 71262306a36Sopenharmony_ci}; 71362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, moxart_mmc_match); 71462306a36Sopenharmony_ci 71562306a36Sopenharmony_cistatic struct platform_driver moxart_mmc_driver = { 71662306a36Sopenharmony_ci .probe = moxart_probe, 71762306a36Sopenharmony_ci .remove_new = moxart_remove, 71862306a36Sopenharmony_ci .driver = { 71962306a36Sopenharmony_ci .name = "mmc-moxart", 72062306a36Sopenharmony_ci .probe_type = PROBE_PREFER_ASYNCHRONOUS, 72162306a36Sopenharmony_ci .of_match_table = moxart_mmc_match, 72262306a36Sopenharmony_ci }, 72362306a36Sopenharmony_ci}; 72462306a36Sopenharmony_cimodule_platform_driver(moxart_mmc_driver); 72562306a36Sopenharmony_ci 72662306a36Sopenharmony_ciMODULE_ALIAS("platform:mmc-moxart"); 72762306a36Sopenharmony_ciMODULE_DESCRIPTION("MOXA ART MMC driver"); 72862306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 72962306a36Sopenharmony_ciMODULE_AUTHOR("Jonas Jensen <jonas.jensen@gmail.com>"); 730