mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-09 05:46:40 +07:00
c783837bc6
The PXA has two transmit FIFOes, each32 byte deep. when one FIFO is full and the other one has been transmitted, they are automatically swapped and DMA is triggered for another 32 byte burst. However, when there is less than 32 bytes left to send, the FIFO swap has to be done manually. This is required for some SDIO transfers which are not required to be multiples of 32 bytes. A DMA completion interrupt is set for each descriptor which length isn't a multiple of 32 in order to force the FIFO swap. While at it, the DMA interrupt handler has been made a bit more resilient against errors. Signed-off-by: Nicolas Pitre <nico@marvell.com> Signed-off-by: Pierre Ossman <drzeus@drzeus.cx>
650 lines
15 KiB
C
650 lines
15 KiB
C
/*
|
|
* linux/drivers/mmc/host/pxa.c - PXA MMCI driver
|
|
*
|
|
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This hardware is really sick:
|
|
* - No way to clear interrupts.
|
|
* - Have to turn off the clock whenever we touch the device.
|
|
* - Doesn't tell you how many data blocks were transferred.
|
|
* Yuck!
|
|
*
|
|
* 1 and 3 byte data transfers not supported
|
|
* max block length up to 1023
|
|
*/
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/mmc/host.h>
|
|
|
|
#include <asm/dma.h>
|
|
#include <asm/io.h>
|
|
#include <asm/scatterlist.h>
|
|
#include <asm/sizes.h>
|
|
|
|
#include <asm/arch/pxa-regs.h>
|
|
#include <asm/arch/mmc.h>
|
|
|
|
#include "pxamci.h"
|
|
|
|
#define DRIVER_NAME "pxa2xx-mci"
|
|
|
|
#define NR_SG 1
|
|
|
|
struct pxamci_host {
|
|
struct mmc_host *mmc;
|
|
spinlock_t lock;
|
|
struct resource *res;
|
|
void __iomem *base;
|
|
int irq;
|
|
int dma;
|
|
unsigned int clkrt;
|
|
unsigned int cmdat;
|
|
unsigned int imask;
|
|
unsigned int power_mode;
|
|
struct pxamci_platform_data *pdata;
|
|
|
|
struct mmc_request *mrq;
|
|
struct mmc_command *cmd;
|
|
struct mmc_data *data;
|
|
|
|
dma_addr_t sg_dma;
|
|
struct pxa_dma_desc *sg_cpu;
|
|
unsigned int dma_len;
|
|
|
|
unsigned int dma_dir;
|
|
};
|
|
|
|
static void pxamci_stop_clock(struct pxamci_host *host)
|
|
{
|
|
if (readl(host->base + MMC_STAT) & STAT_CLK_EN) {
|
|
unsigned long timeout = 10000;
|
|
unsigned int v;
|
|
|
|
writel(STOP_CLOCK, host->base + MMC_STRPCL);
|
|
|
|
do {
|
|
v = readl(host->base + MMC_STAT);
|
|
if (!(v & STAT_CLK_EN))
|
|
break;
|
|
udelay(1);
|
|
} while (timeout--);
|
|
|
|
if (v & STAT_CLK_EN)
|
|
dev_err(mmc_dev(host->mmc), "unable to stop clock\n");
|
|
}
|
|
}
|
|
|
|
static void pxamci_enable_irq(struct pxamci_host *host, unsigned int mask)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&host->lock, flags);
|
|
host->imask &= ~mask;
|
|
writel(host->imask, host->base + MMC_I_MASK);
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
}
|
|
|
|
static void pxamci_disable_irq(struct pxamci_host *host, unsigned int mask)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&host->lock, flags);
|
|
host->imask |= mask;
|
|
writel(host->imask, host->base + MMC_I_MASK);
|
|
spin_unlock_irqrestore(&host->lock, flags);
|
|
}
|
|
|
|
static void pxamci_setup_data(struct pxamci_host *host, struct mmc_data *data)
|
|
{
|
|
unsigned int nob = data->blocks;
|
|
unsigned long long clks;
|
|
unsigned int timeout;
|
|
u32 dcmd;
|
|
int i;
|
|
|
|
host->data = data;
|
|
|
|
if (data->flags & MMC_DATA_STREAM)
|
|
nob = 0xffff;
|
|
|
|
writel(nob, host->base + MMC_NOB);
|
|
writel(data->blksz, host->base + MMC_BLKLEN);
|
|
|
|
clks = (unsigned long long)data->timeout_ns * CLOCKRATE;
|
|
do_div(clks, 1000000000UL);
|
|
timeout = (unsigned int)clks + (data->timeout_clks << host->clkrt);
|
|
writel((timeout + 255) / 256, host->base + MMC_RDTO);
|
|
|
|
if (data->flags & MMC_DATA_READ) {
|
|
host->dma_dir = DMA_FROM_DEVICE;
|
|
dcmd = DCMD_INCTRGADDR | DCMD_FLOWTRG;
|
|
DRCMRTXMMC = 0;
|
|
DRCMRRXMMC = host->dma | DRCMR_MAPVLD;
|
|
} else {
|
|
host->dma_dir = DMA_TO_DEVICE;
|
|
dcmd = DCMD_INCSRCADDR | DCMD_FLOWSRC;
|
|
DRCMRRXMMC = 0;
|
|
DRCMRTXMMC = host->dma | DRCMR_MAPVLD;
|
|
}
|
|
|
|
dcmd |= DCMD_BURST32 | DCMD_WIDTH1;
|
|
|
|
host->dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
|
|
host->dma_dir);
|
|
|
|
for (i = 0; i < host->dma_len; i++) {
|
|
unsigned int length = sg_dma_len(&data->sg[i]);
|
|
host->sg_cpu[i].dcmd = dcmd | length;
|
|
if (length & 31 && !(data->flags & MMC_DATA_READ))
|
|
host->sg_cpu[i].dcmd |= DCMD_ENDIRQEN;
|
|
if (data->flags & MMC_DATA_READ) {
|
|
host->sg_cpu[i].dsadr = host->res->start + MMC_RXFIFO;
|
|
host->sg_cpu[i].dtadr = sg_dma_address(&data->sg[i]);
|
|
} else {
|
|
host->sg_cpu[i].dsadr = sg_dma_address(&data->sg[i]);
|
|
host->sg_cpu[i].dtadr = host->res->start + MMC_TXFIFO;
|
|
}
|
|
host->sg_cpu[i].ddadr = host->sg_dma + (i + 1) *
|
|
sizeof(struct pxa_dma_desc);
|
|
}
|
|
host->sg_cpu[host->dma_len - 1].ddadr = DDADR_STOP;
|
|
wmb();
|
|
|
|
DDADR(host->dma) = host->sg_dma;
|
|
DCSR(host->dma) = DCSR_RUN;
|
|
}
|
|
|
|
static void pxamci_start_cmd(struct pxamci_host *host, struct mmc_command *cmd, unsigned int cmdat)
|
|
{
|
|
WARN_ON(host->cmd != NULL);
|
|
host->cmd = cmd;
|
|
|
|
if (cmd->flags & MMC_RSP_BUSY)
|
|
cmdat |= CMDAT_BUSY;
|
|
|
|
#define RSP_TYPE(x) ((x) & ~(MMC_RSP_BUSY|MMC_RSP_OPCODE))
|
|
switch (RSP_TYPE(mmc_resp_type(cmd))) {
|
|
case RSP_TYPE(MMC_RSP_R1): /* r1, r1b, r6, r7 */
|
|
cmdat |= CMDAT_RESP_SHORT;
|
|
break;
|
|
case RSP_TYPE(MMC_RSP_R3):
|
|
cmdat |= CMDAT_RESP_R3;
|
|
break;
|
|
case RSP_TYPE(MMC_RSP_R2):
|
|
cmdat |= CMDAT_RESP_R2;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
writel(cmd->opcode, host->base + MMC_CMD);
|
|
writel(cmd->arg >> 16, host->base + MMC_ARGH);
|
|
writel(cmd->arg & 0xffff, host->base + MMC_ARGL);
|
|
writel(cmdat, host->base + MMC_CMDAT);
|
|
writel(host->clkrt, host->base + MMC_CLKRT);
|
|
|
|
writel(START_CLOCK, host->base + MMC_STRPCL);
|
|
|
|
pxamci_enable_irq(host, END_CMD_RES);
|
|
}
|
|
|
|
static void pxamci_finish_request(struct pxamci_host *host, struct mmc_request *mrq)
|
|
{
|
|
host->mrq = NULL;
|
|
host->cmd = NULL;
|
|
host->data = NULL;
|
|
mmc_request_done(host->mmc, mrq);
|
|
}
|
|
|
|
static int pxamci_cmd_done(struct pxamci_host *host, unsigned int stat)
|
|
{
|
|
struct mmc_command *cmd = host->cmd;
|
|
int i;
|
|
u32 v;
|
|
|
|
if (!cmd)
|
|
return 0;
|
|
|
|
host->cmd = NULL;
|
|
|
|
/*
|
|
* Did I mention this is Sick. We always need to
|
|
* discard the upper 8 bits of the first 16-bit word.
|
|
*/
|
|
v = readl(host->base + MMC_RES) & 0xffff;
|
|
for (i = 0; i < 4; i++) {
|
|
u32 w1 = readl(host->base + MMC_RES) & 0xffff;
|
|
u32 w2 = readl(host->base + MMC_RES) & 0xffff;
|
|
cmd->resp[i] = v << 24 | w1 << 8 | w2 >> 8;
|
|
v = w2;
|
|
}
|
|
|
|
if (stat & STAT_TIME_OUT_RESPONSE) {
|
|
cmd->error = -ETIMEDOUT;
|
|
} else if (stat & STAT_RES_CRC_ERR && cmd->flags & MMC_RSP_CRC) {
|
|
#ifdef CONFIG_PXA27x
|
|
/*
|
|
* workaround for erratum #42:
|
|
* Intel PXA27x Family Processor Specification Update Rev 001
|
|
* A bogus CRC error can appear if the msb of a 136 bit
|
|
* response is a one.
|
|
*/
|
|
if (cmd->flags & MMC_RSP_136 && cmd->resp[0] & 0x80000000) {
|
|
pr_debug("ignoring CRC from command %d - *risky*\n", cmd->opcode);
|
|
} else
|
|
#endif
|
|
cmd->error = -EILSEQ;
|
|
}
|
|
|
|
pxamci_disable_irq(host, END_CMD_RES);
|
|
if (host->data && !cmd->error) {
|
|
pxamci_enable_irq(host, DATA_TRAN_DONE);
|
|
} else {
|
|
pxamci_finish_request(host, host->mrq);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int pxamci_data_done(struct pxamci_host *host, unsigned int stat)
|
|
{
|
|
struct mmc_data *data = host->data;
|
|
|
|
if (!data)
|
|
return 0;
|
|
|
|
DCSR(host->dma) = 0;
|
|
dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->dma_len,
|
|
host->dma_dir);
|
|
|
|
if (stat & STAT_READ_TIME_OUT)
|
|
data->error = -ETIMEDOUT;
|
|
else if (stat & (STAT_CRC_READ_ERROR|STAT_CRC_WRITE_ERROR))
|
|
data->error = -EILSEQ;
|
|
|
|
/*
|
|
* There appears to be a hardware design bug here. There seems to
|
|
* be no way to find out how much data was transferred to the card.
|
|
* This means that if there was an error on any block, we mark all
|
|
* data blocks as being in error.
|
|
*/
|
|
if (!data->error)
|
|
data->bytes_xfered = data->blocks * data->blksz;
|
|
else
|
|
data->bytes_xfered = 0;
|
|
|
|
pxamci_disable_irq(host, DATA_TRAN_DONE);
|
|
|
|
host->data = NULL;
|
|
if (host->mrq->stop) {
|
|
pxamci_stop_clock(host);
|
|
pxamci_start_cmd(host, host->mrq->stop, host->cmdat);
|
|
} else {
|
|
pxamci_finish_request(host, host->mrq);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static irqreturn_t pxamci_irq(int irq, void *devid)
|
|
{
|
|
struct pxamci_host *host = devid;
|
|
unsigned int ireg;
|
|
int handled = 0;
|
|
|
|
ireg = readl(host->base + MMC_I_REG) & ~readl(host->base + MMC_I_MASK);
|
|
|
|
if (ireg) {
|
|
unsigned stat = readl(host->base + MMC_STAT);
|
|
|
|
pr_debug("PXAMCI: irq %08x stat %08x\n", ireg, stat);
|
|
|
|
if (ireg & END_CMD_RES)
|
|
handled |= pxamci_cmd_done(host, stat);
|
|
if (ireg & DATA_TRAN_DONE)
|
|
handled |= pxamci_data_done(host, stat);
|
|
if (ireg & SDIO_INT) {
|
|
mmc_signal_sdio_irq(host->mmc);
|
|
handled = 1;
|
|
}
|
|
}
|
|
|
|
return IRQ_RETVAL(handled);
|
|
}
|
|
|
|
static void pxamci_request(struct mmc_host *mmc, struct mmc_request *mrq)
|
|
{
|
|
struct pxamci_host *host = mmc_priv(mmc);
|
|
unsigned int cmdat;
|
|
|
|
WARN_ON(host->mrq != NULL);
|
|
|
|
host->mrq = mrq;
|
|
|
|
pxamci_stop_clock(host);
|
|
|
|
cmdat = host->cmdat;
|
|
host->cmdat &= ~CMDAT_INIT;
|
|
|
|
if (mrq->data) {
|
|
pxamci_setup_data(host, mrq->data);
|
|
|
|
cmdat &= ~CMDAT_BUSY;
|
|
cmdat |= CMDAT_DATAEN | CMDAT_DMAEN;
|
|
if (mrq->data->flags & MMC_DATA_WRITE)
|
|
cmdat |= CMDAT_WRITE;
|
|
|
|
if (mrq->data->flags & MMC_DATA_STREAM)
|
|
cmdat |= CMDAT_STREAM;
|
|
}
|
|
|
|
pxamci_start_cmd(host, mrq->cmd, cmdat);
|
|
}
|
|
|
|
static int pxamci_get_ro(struct mmc_host *mmc)
|
|
{
|
|
struct pxamci_host *host = mmc_priv(mmc);
|
|
|
|
if (host->pdata && host->pdata->get_ro)
|
|
return host->pdata->get_ro(mmc_dev(mmc));
|
|
/* Host doesn't support read only detection so assume writeable */
|
|
return 0;
|
|
}
|
|
|
|
static void pxamci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
|
|
{
|
|
struct pxamci_host *host = mmc_priv(mmc);
|
|
|
|
if (ios->clock) {
|
|
unsigned int clk = CLOCKRATE / ios->clock;
|
|
if (CLOCKRATE / clk > ios->clock)
|
|
clk <<= 1;
|
|
host->clkrt = fls(clk) - 1;
|
|
pxa_set_cken(CKEN_MMC, 1);
|
|
|
|
/*
|
|
* we write clkrt on the next command
|
|
*/
|
|
} else {
|
|
pxamci_stop_clock(host);
|
|
pxa_set_cken(CKEN_MMC, 0);
|
|
}
|
|
|
|
if (host->power_mode != ios->power_mode) {
|
|
host->power_mode = ios->power_mode;
|
|
|
|
if (host->pdata && host->pdata->setpower)
|
|
host->pdata->setpower(mmc_dev(mmc), ios->vdd);
|
|
|
|
if (ios->power_mode == MMC_POWER_ON)
|
|
host->cmdat |= CMDAT_INIT;
|
|
}
|
|
|
|
if (ios->bus_width == MMC_BUS_WIDTH_4)
|
|
host->cmdat |= CMDAT_SD_4DAT;
|
|
else
|
|
host->cmdat &= ~CMDAT_SD_4DAT;
|
|
|
|
pr_debug("PXAMCI: clkrt = %x cmdat = %x\n",
|
|
host->clkrt, host->cmdat);
|
|
}
|
|
|
|
static void pxamci_enable_sdio_irq(struct mmc_host *host, int enable)
|
|
{
|
|
struct pxamci_host *pxa_host = mmc_priv(host);
|
|
|
|
if (enable)
|
|
pxamci_enable_irq(pxa_host, SDIO_INT);
|
|
else
|
|
pxamci_disable_irq(pxa_host, SDIO_INT);
|
|
}
|
|
|
|
static const struct mmc_host_ops pxamci_ops = {
|
|
.request = pxamci_request,
|
|
.get_ro = pxamci_get_ro,
|
|
.set_ios = pxamci_set_ios,
|
|
.enable_sdio_irq = pxamci_enable_sdio_irq,
|
|
};
|
|
|
|
static void pxamci_dma_irq(int dma, void *devid)
|
|
{
|
|
struct pxamci_host *host = devid;
|
|
int dcsr = DCSR(dma);
|
|
DCSR(dma) = dcsr & ~DCSR_STOPIRQEN;
|
|
|
|
if (dcsr & DCSR_ENDINTR) {
|
|
writel(BUF_PART_FULL, host->base + MMC_PRTBUF);
|
|
} else {
|
|
printk(KERN_ERR "%s: DMA error on channel %d (DCSR=%#x)\n",
|
|
mmc_hostname(host->mmc), dma, dcsr);
|
|
host->data->error = -EIO;
|
|
pxamci_data_done(host, 0);
|
|
}
|
|
}
|
|
|
|
static irqreturn_t pxamci_detect_irq(int irq, void *devid)
|
|
{
|
|
struct pxamci_host *host = mmc_priv(devid);
|
|
|
|
mmc_detect_change(devid, host->pdata->detect_delay);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int pxamci_probe(struct platform_device *pdev)
|
|
{
|
|
struct mmc_host *mmc;
|
|
struct pxamci_host *host = NULL;
|
|
struct resource *r;
|
|
int ret, irq;
|
|
|
|
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
irq = platform_get_irq(pdev, 0);
|
|
if (!r || irq < 0)
|
|
return -ENXIO;
|
|
|
|
r = request_mem_region(r->start, SZ_4K, DRIVER_NAME);
|
|
if (!r)
|
|
return -EBUSY;
|
|
|
|
mmc = mmc_alloc_host(sizeof(struct pxamci_host), &pdev->dev);
|
|
if (!mmc) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
mmc->ops = &pxamci_ops;
|
|
mmc->f_min = CLOCKRATE_MIN;
|
|
mmc->f_max = CLOCKRATE_MAX;
|
|
|
|
/*
|
|
* We can do SG-DMA, but we don't because we never know how much
|
|
* data we successfully wrote to the card.
|
|
*/
|
|
mmc->max_phys_segs = NR_SG;
|
|
|
|
/*
|
|
* Our hardware DMA can handle a maximum of one page per SG entry.
|
|
*/
|
|
mmc->max_seg_size = PAGE_SIZE;
|
|
|
|
/*
|
|
* Block length register is only 10 bits before PXA27x.
|
|
*/
|
|
mmc->max_blk_size = (cpu_is_pxa21x() || cpu_is_pxa25x()) ? 1023 : 2048;
|
|
|
|
/*
|
|
* Block count register is 16 bits.
|
|
*/
|
|
mmc->max_blk_count = 65535;
|
|
|
|
host = mmc_priv(mmc);
|
|
host->mmc = mmc;
|
|
host->dma = -1;
|
|
host->pdata = pdev->dev.platform_data;
|
|
mmc->ocr_avail = host->pdata ?
|
|
host->pdata->ocr_mask :
|
|
MMC_VDD_32_33|MMC_VDD_33_34;
|
|
mmc->caps = 0;
|
|
host->cmdat = 0;
|
|
if (!cpu_is_pxa21x() && !cpu_is_pxa25x()) {
|
|
mmc->caps |= MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;
|
|
host->cmdat |= CMDAT_SDIO_INT_EN;
|
|
}
|
|
|
|
host->sg_cpu = dma_alloc_coherent(&pdev->dev, PAGE_SIZE, &host->sg_dma, GFP_KERNEL);
|
|
if (!host->sg_cpu) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
spin_lock_init(&host->lock);
|
|
host->res = r;
|
|
host->irq = irq;
|
|
host->imask = MMC_I_MASK_ALL;
|
|
|
|
host->base = ioremap(r->start, SZ_4K);
|
|
if (!host->base) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Ensure that the host controller is shut down, and setup
|
|
* with our defaults.
|
|
*/
|
|
pxamci_stop_clock(host);
|
|
writel(0, host->base + MMC_SPI);
|
|
writel(64, host->base + MMC_RESTO);
|
|
writel(host->imask, host->base + MMC_I_MASK);
|
|
|
|
host->dma = pxa_request_dma(DRIVER_NAME, DMA_PRIO_LOW,
|
|
pxamci_dma_irq, host);
|
|
if (host->dma < 0) {
|
|
ret = -EBUSY;
|
|
goto out;
|
|
}
|
|
|
|
ret = request_irq(host->irq, pxamci_irq, 0, DRIVER_NAME, host);
|
|
if (ret)
|
|
goto out;
|
|
|
|
platform_set_drvdata(pdev, mmc);
|
|
|
|
if (host->pdata && host->pdata->init)
|
|
host->pdata->init(&pdev->dev, pxamci_detect_irq, mmc);
|
|
|
|
mmc_add_host(mmc);
|
|
|
|
return 0;
|
|
|
|
out:
|
|
if (host) {
|
|
if (host->dma >= 0)
|
|
pxa_free_dma(host->dma);
|
|
if (host->base)
|
|
iounmap(host->base);
|
|
if (host->sg_cpu)
|
|
dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
|
|
}
|
|
if (mmc)
|
|
mmc_free_host(mmc);
|
|
release_resource(r);
|
|
return ret;
|
|
}
|
|
|
|
static int pxamci_remove(struct platform_device *pdev)
|
|
{
|
|
struct mmc_host *mmc = platform_get_drvdata(pdev);
|
|
|
|
platform_set_drvdata(pdev, NULL);
|
|
|
|
if (mmc) {
|
|
struct pxamci_host *host = mmc_priv(mmc);
|
|
|
|
if (host->pdata && host->pdata->exit)
|
|
host->pdata->exit(&pdev->dev, mmc);
|
|
|
|
mmc_remove_host(mmc);
|
|
|
|
pxamci_stop_clock(host);
|
|
writel(TXFIFO_WR_REQ|RXFIFO_RD_REQ|CLK_IS_OFF|STOP_CMD|
|
|
END_CMD_RES|PRG_DONE|DATA_TRAN_DONE,
|
|
host->base + MMC_I_MASK);
|
|
|
|
DRCMRRXMMC = 0;
|
|
DRCMRTXMMC = 0;
|
|
|
|
free_irq(host->irq, host);
|
|
pxa_free_dma(host->dma);
|
|
iounmap(host->base);
|
|
dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
|
|
|
|
release_resource(host->res);
|
|
|
|
mmc_free_host(mmc);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int pxamci_suspend(struct platform_device *dev, pm_message_t state)
|
|
{
|
|
struct mmc_host *mmc = platform_get_drvdata(dev);
|
|
int ret = 0;
|
|
|
|
if (mmc)
|
|
ret = mmc_suspend_host(mmc, state);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int pxamci_resume(struct platform_device *dev)
|
|
{
|
|
struct mmc_host *mmc = platform_get_drvdata(dev);
|
|
int ret = 0;
|
|
|
|
if (mmc)
|
|
ret = mmc_resume_host(mmc);
|
|
|
|
return ret;
|
|
}
|
|
#else
|
|
#define pxamci_suspend NULL
|
|
#define pxamci_resume NULL
|
|
#endif
|
|
|
|
static struct platform_driver pxamci_driver = {
|
|
.probe = pxamci_probe,
|
|
.remove = pxamci_remove,
|
|
.suspend = pxamci_suspend,
|
|
.resume = pxamci_resume,
|
|
.driver = {
|
|
.name = DRIVER_NAME,
|
|
},
|
|
};
|
|
|
|
static int __init pxamci_init(void)
|
|
{
|
|
return platform_driver_register(&pxamci_driver);
|
|
}
|
|
|
|
static void __exit pxamci_exit(void)
|
|
{
|
|
platform_driver_unregister(&pxamci_driver);
|
|
}
|
|
|
|
module_init(pxamci_init);
|
|
module_exit(pxamci_exit);
|
|
|
|
MODULE_DESCRIPTION("PXA Multimedia Card Interface Driver");
|
|
MODULE_LICENSE("GPL");
|