Re: [PATCH] mmc: add new au6601 driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Oleksij,

Sorry for a late reply.

On 8 May 2014 11:58, Oleksij Rempel <linux@xxxxxxxxxxxxxxxx> wrote:
> This driver is based on documentation which was based on my RE-work and
> comparision with other MMC drivers.
> It works in legacy mode and can provide 20MB/s R/W spead for most modern
> SD cards, even if at least 40MB/s should be possible on this hardware.
> It was not possible provide description for all register. But some of them
> are important to make this hardware work in some unknown way.
>
> Biggest part of RE-work was done by emulating AU6601 in QEMU.
>
> Signed-off-by: Oleksij Rempel <linux@xxxxxxxxxxxxxxxx>
> ---
>  drivers/mmc/host/Kconfig  |    8 +
>  drivers/mmc/host/Makefile |    1 +
>  drivers/mmc/host/au6601.c | 1276 +++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 1285 insertions(+)
>  create mode 100644 drivers/mmc/host/au6601.c
>
> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
> index 8aaf8c1..99b309f 100644
> --- a/drivers/mmc/host/Kconfig
> +++ b/drivers/mmc/host/Kconfig
> @@ -315,6 +315,14 @@ config MMC_WBSD
>
>           If unsure, say N.
>
> +config MMC_AU6601
> +       tristate "Alcor Micro AU6601"
> +       help
> +         This selects the Alcor Micro Multimedia card interface.
> +
> +         If unsure, say N.
> +
> +
>  config MMC_AU1X
>         tristate "Alchemy AU1XX0 MMC Card Interface support"
>         depends on MIPS_ALCHEMY
> diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
> index 0c8aa5e..8f3c64c 100644
> --- a/drivers/mmc/host/Makefile
> +++ b/drivers/mmc/host/Makefile
> @@ -18,6 +18,7 @@ obj-$(CONFIG_MMC_SDHCI_SIRF)          += sdhci-sirf.o
>  obj-$(CONFIG_MMC_SDHCI_SPEAR)  += sdhci-spear.o
>  obj-$(CONFIG_MMC_WBSD)         += wbsd.o
>  obj-$(CONFIG_MMC_AU1X)         += au1xmmc.o
> +obj-$(CONFIG_MMC_AU6601)       += au6601.o
>  obj-$(CONFIG_MMC_OMAP)         += omap.o
>  obj-$(CONFIG_MMC_OMAP_HS)      += omap_hsmmc.o
>  obj-$(CONFIG_MMC_ATMELMCI)     += atmel-mci.o
> diff --git a/drivers/mmc/host/au6601.c b/drivers/mmc/host/au6601.c
> new file mode 100644
> index 0000000..92d639f
> --- /dev/null
> +++ b/drivers/mmc/host/au6601.c
> @@ -0,0 +1,1276 @@
> +/*
> + * Copyright (C) 2014 Oleksij Rempel.
> + *
> + * Authors: Oleksij Rempel <linux@xxxxxxxxxxxxxxxx>
> + *
> + * This software is licensed under the terms of the GNU General Public
> + * License version 2, as published by the Free Software Foundation, and
> + * may be copied, distributed, and modified under those terms.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + */
> +
> +
> +#include <linux/delay.h>
> +#include <linux/pci.h>
> +#include <linux/module.h>
> +#include <linux/io.h>
> +#include <linux/irq.h>
> +#include <linux/interrupt.h>
> +
> +#include <linux/mmc/host.h>
> +#include <linux/mmc/mmc.h>
> +
> +#define DRVNAME                        "au6601-pci"
> +#define PCI_ID_ALCOR_MICRO     0x1aea
> +#define PCI_ID_AU6601          0x6601
> +
> +#define MHZ_TO_HZ(freq)        ((freq) * 1000 * 1000)
> +
> +#define AU6601_MIN_CLOCK               (150 * 1000)
> +#define AU6601_MAX_CLOCK               MHZ_TO_HZ(208)
> +#define AU6601_MAX_SEGMENTS            512
> +#define AU6601_MAX_BLOCK_LENGTH                512
> +#define AU6601_MAX_DMA_BLOCKS          8
> +#define AU6601_MAX_BLOCK_COUNT         65536
> +
> +/* SDMA phy address. Higer then 0x0800.0000? */
> +#define AU6601_REG_SDMA_ADDR   0x00
> +/* ADMA block count? AU6621 only. */
> +#define REG_05 0x05
> +/* PIO */
> +#define AU6601_REG_BUFFER      0x08
> +/* ADMA ctrl? AU6621 only. */
> +#define REG_0C 0x0c
> +/* ADMA phy address. AU6621 only. */
> +#define REG_10 0x10
> +/* CMD index */
> +#define AU6601_REG_CMD_OPCODE  0x23
> +/* CMD parametr */
> +#define AU6601_REG_CMD_ARG     0x24
> +/* CMD response 4x4 Bytes */
> +#define AU6601_REG_CMD_RSP0    0x30
> +#define AU6601_REG_CMD_RSP1    0x34
> +#define AU6601_REG_CMD_RSP2    0x38
> +#define AU6601_REG_CMD_RSP3    0x3C
> +/* LED ctrl? */
> +#define REG_51 0x51
> +/* ??? */
> +#define REG_52 0x52
> +/* LED related? Always toggled BIT0 */
> +#define REG_61 0x61
> +/* Same as REG_61? */
> +#define REG_63 0x63
> +/* ??? */
> +#define REG_69 0x69
> +/* Block size for SDMA or PIO */
> +#define AU6601_REG_BLOCK_SIZE  0x6c
> +/* Some power related reg, used together with REG_7A */
> +#define REG_70 0x70
> +/* PLL ctrl */
> +#define AU6601_REG_PLL_CTRL    0x72
> +/* ??? */
> +#define REG_74 0x74
> +/* ??? */
> +#define REG_75 0x75
> +/* card slot state? */
> +#define REG_76 0x76
> +/* ??? */
> +#define REG_77 0x77
> +/* looks like soft reset? */
> +#define AU6601_REG_SW_RESET    0x79
> + #define AU6601_RESET_UNK      BIT(7)  /* unknown bit */
> + #define AU6601_RESET_DATA     BIT(3)
> + #define AU6601_RESET_CMD      BIT(0)
> +/* see REG_70 */
> +#define REG_7A 0x7a
> +/* ??? Padding? Timeing? */
> +#define REG_7B 0x7b
> +/* ??? Padding? Timeing? */
> +#define REG_7C 0x7c
> +/* ??? Padding? Timeing? */
> +#define REG_7D 0x7d
> +/* read EEPROM? */
> +#define REG_7F 0x7f
> +
> +#define AU6601_REG_CMD_CTRL    0x81
> +#define AU6601_REG_BUS_CTRL    0x82
> + #define AU6601_BUS_WIDTH_4BIT BIT(5)
> +#define REG_83 0x83
> +
> +#define AU6601_REG_BUS_STATUS  0x84
> + #define AU6601_BUS_STAT_CMD   BIT(15)
> +/* BIT(4) - BIT(7) are permanently 1.
> + * May be reseved or not attached DAT4-DAT7 */
> + #define AU6601_BUS_STAT_DAT3          BIT(3)
> + #define AU6601_BUS_STAT_DAT2          BIT(2)
> + #define AU6601_BUS_STAT_DAT1          BIT(1)
> + #define AU6601_BUS_STAT_DAT0          BIT(0)
> + #define AU6601_BUS_STAT_DAT_MASK      0xf
> +#define REG_85 0x85
> +/* ??? */
> +#define REG_86 0x86
> +#define AU6601_REG_INT_STATUS  0x90 /* IRQ intmask */
> +#define AU6601_REG_INT_ENABLE  0x94
> +/* ??? */
> +#define REG_A1 0xa1
> +/* ??? */
> +#define REG_A2 0xa2
> +/* ??? */
> +#define REG_A3 0xa3
> +/* ??? */
> +#define REG_B0 0xb0
> +/* ??? */
> +#define REG_B4 0xb4
> +
> + /* AU6601_REG_INT_STATUS is identical or almost identical with sdhci.h */
> + /* OK - are tested and confirmed bits */
> + #define  AU6601_INT_RESPONSE          0x00000001      /* ok */
> + #define  AU6601_INT_DATA_END          0x00000002      /* fifo, ok */
> + #define  AU6601_INT_BLK_GAP           0x00000004
> + #define  AU6601_INT_DMA_END           0x00000008
> + #define  AU6601_INT_SPACE_AVAIL       0x00000010      /* fifo, ok */
> + #define  AU6601_INT_DATA_AVAIL                0x00000020      /* fifo, ok */
> + #define  AU6601_INT_CARD_REMOVE       0x00000040
> + #define  AU6601_INT_CARD_INSERT       0x00000080      /* 0x40 and 0x80 flip */
> + #define  AU6601_INT_CARD_INT          0x00000100
> + #define  AU6601_INT_ERROR             0x00008000      /* ok */
> + #define  AU6601_INT_TIMEOUT           0x00010000      /* seems to be ok */
> + #define  AU6601_INT_CRC               0x00020000      /* seems to be ok */
> + #define  AU6601_INT_END_BIT           0x00040000
> + #define  AU6601_INT_INDEX             0x00080000
> + #define  AU6601_INT_DATA_TIMEOUT      0x00100000
> + #define  AU6601_INT_DATA_CRC          0x00200000
> + #define  AU6601_INT_DATA_END_BIT      0x00400000
> + #define  AU6601_INT_BUS_POWER         0x00800000
> + #define  AU6601_INT_ACMD12ERR         0x01000000
> + #define  AU6601_INT_ADMA_ERROR                0x02000000
> +
> + #define  AU6601_INT_NORMAL_MASK       0x00007FFF
> + #define  AU6601_INT_ERROR_MASK                0xFFFF8000
> +
> +/* magic 0xF0001 */
> + #define  AU6601_INT_CMD_MASK  (AU6601_INT_RESPONSE | AU6601_INT_TIMEOUT | \
> +               AU6601_INT_CRC | AU6601_INT_END_BIT | AU6601_INT_INDEX)
> +/* magic 0x70003A */
> + #define  AU6601_INT_DATA_MASK (AU6601_INT_DATA_END | AU6601_INT_DMA_END | \
> +               AU6601_INT_DATA_AVAIL | AU6601_INT_SPACE_AVAIL | \
> +               AU6601_INT_DATA_TIMEOUT | AU6601_INT_DATA_CRC | \
> +               AU6601_INT_DATA_END_BIT)
> + #define AU6601_INT_ALL_MASK   ((uint32_t)-1)
> +
> +bool disable_dma = 0;

Could this not be apart of the struct au6601_host? Or you might even
be able to use "dma_on", which is already there?

> +
> +struct au6601_host {
> +       struct pci_dev *pdev;
> +       struct  device *dev;
> +       void __iomem *iobase;
> +       void __iomem *virt_base;
> +       dma_addr_t phys_base;
> +
> +       struct mmc_host *mmc;
> +       struct mmc_request *mrq;
> +       struct mmc_command *cmd;
> +       struct mmc_data *data;
> +       unsigned int data_early:1;      /* Data finished before cmd */
> +       unsigned int dma_on:1;
> +       unsigned int trigger_dma_dac:1; /* Trigger Data after Command.
> +                                        * In some cases data ragister
> +                                        * should be triggered after
> +                                        * command was done */
> +
> +       spinlock_t lock;
> +
> +       struct tasklet_struct card_tasklet;
> +       struct tasklet_struct finish_tasklet;

Please try using threaded irqs in favor of tasklets.

> +
> +       struct timer_list timer;
> +
> +       struct sg_mapping_iter sg_miter;        /* SG state for PIO */
> +       unsigned int blocks;            /* remaining PIO blocks */
> +       unsigned int requested_blocks;          /* count of requested */
> +       int sg_count;      /* Mapped sg entries */
> +};
> +
> +static void au6601_send_cmd(struct au6601_host *host,
> +                           struct mmc_command *cmd);
> +
> +static void au6601_prepare_data(struct au6601_host *host,
> +                               struct mmc_command *cmd);
> +static void au6601_finish_data(struct au6601_host *host);
> +
> +static const struct pci_device_id pci_ids[] = {
> +       {
> +               .vendor  = PCI_ID_ALCOR_MICRO,
> +               .device  = PCI_ID_AU6601,
> +               .subvendor      = PCI_ANY_ID,
> +               .subdevice      = PCI_ANY_ID,
> +       },
> +       { /* end: all zeroes */ },
> +};
> +MODULE_DEVICE_TABLE(pci, pci_ids);
> +
> +static inline void au6601_rmw(void __iomem *reg, u32 clear, u32 set)
> +{
> +       u32 var;
> +
> +       var = ioread32(reg);
> +       var &= ~clear;
> +       var |= set;
> +       iowrite32(var, reg);
> +}
> +
> +static void au6601_clear_set_irqs(struct au6601_host *host, u32 clear, u32 set)
> +{
> +       au6601_rmw(host->iobase + AU6601_REG_INT_ENABLE, clear, set);
> +}
> +
> +static void au6601_clear_set_reg86(struct au6601_host *host, u32 clear, u32 set)
> +{
> +       au6601_rmw(host->iobase + REG_86, clear, set);
> +}
> +
> +/*
> + * check if one of data line is pulled down
> + */
> +static inline int au6601_card_busy(struct au6601_host *host)
> +{
> +       u8 status;
> +
> +       status = (ioread8(host->iobase + AU6601_REG_BUS_STATUS) &
> +               AU6601_BUS_STAT_DAT_MASK);
> +       /* If all data lines are up, then card is not busy */
> +       if (status == (AU6601_BUS_STAT_DAT0 | AU6601_BUS_STAT_DAT1 |
> +                      AU6601_BUS_STAT_DAT2 | AU6601_BUS_STAT_DAT3))
> +               return 0;
> +
> +       return 1;
> +}
> +
> +/* val = 0x1 abort command; 0x8 abort data? */
> +static void au6601_reset(struct au6601_host *host, u8 val)
> +{
> +       int i;
> +       iowrite8(val | AU6601_RESET_UNK, host->iobase + AU6601_REG_SW_RESET);
> +       for (i = 0; i < 100; i++) {
> +               if (!(ioread8(host->iobase + AU6601_REG_SW_RESET) & val))
> +                       return;
> +               udelay(50);
> +       }
> +       dev_err(host->dev, "%s: timeout\n", __func__);
> +}
> +
> +/*
> + * - 0x8       only Vcc is on
> + * - 0x1       Vcc and other pins are on
> + * - 0x1 | 0x8 like 0x1, but DAT2 is off
> + */
> +static void au6601_set_power(struct au6601_host *host,
> +                            unsigned int value, unsigned int set)
> +{
> +       u8 tmp1, tmp2;
> +
> +       tmp1 = ioread8(host->iobase + REG_70);
> +       tmp2 = ioread8(host->iobase + REG_7A);
> +       if (set) {
> +               iowrite8(tmp1 | value, host->iobase + REG_70);
> +               msleep(20);
> +               iowrite8(tmp2 | value, host->iobase + REG_7A);
> +       } else {
> +               iowrite8(tmp2 & ~value, host->iobase + REG_7A);
> +               iowrite8(tmp1 & ~value, host->iobase + REG_70);
> +       }
> +}
> +
> +static void au6601_trigger_data_transfer(struct au6601_host *host,
> +               unsigned int dma)
> +{
> +       struct mmc_data *data = host->data;
> +       u8 ctrl = 0;
> +
> +       BUG_ON(data == NULL);

Do you really need BUG_ON?

Also, please go through the complete patch to look for these
BUG/BUG_ON, I think you shouldn't use them in most of the cases.

> +       WARN_ON_ONCE(host->dma_on == 1);

Why?

> +
> +       if (data->flags & MMC_DATA_WRITE)
> +               ctrl |= 0x80;
> +
> +       if (dma) {
> +               iowrite32(host->phys_base, host->iobase + AU6601_REG_SDMA_ADDR);
> +               ctrl |= 0x40;
> +               host->dma_on = 1;
> +
> +               if (data->flags & MMC_DATA_WRITE)
> +                       goto done;
> +               /* prepare first DMA buffer for write operation */
> +               if (host->blocks > AU6601_MAX_DMA_BLOCKS)
> +                       host->requested_blocks = AU6601_MAX_DMA_BLOCKS;
> +               else
> +                       host->requested_blocks = host->blocks;
> +
> +       }
> +
> +done:
> +       iowrite32(data->blksz * host->requested_blocks,
> +               host->iobase + AU6601_REG_BLOCK_SIZE);
> +       iowrite8(ctrl | 0x1, host->iobase + REG_83);
> +}
> +
> +/*****************************************************************************\
> + *                                                                          *
> + * Core functions                                                           *
> + *                                                                          *
> +\*****************************************************************************/
> +
> +static void au6601_read_block(struct au6601_host *host)
> +{
> +       unsigned long flags;
> +       size_t blksize, len, chunk;
> +       u32 uninitialized_var(scratch);
> +       void __iomem *virt_base = host->virt_base;
> +       u8 *buf;
> +
> +       dev_dbg(host->dev, "PIO reading\n");
> +
> +       blksize = host->data->blksz * host->requested_blocks;
> +       chunk = 0;
> +
> +       local_irq_save(flags);
> +
> +       while (blksize) {
> +               if (!sg_miter_next(&host->sg_miter))
> +                       BUG();
> +
> +               len = min(host->sg_miter.length, blksize);
> +
> +               blksize -= len;
> +               host->sg_miter.consumed = len;
> +
> +               buf = host->sg_miter.addr;
> +
> +               if (host->dma_on) {
> +                       memcpy_fromio(buf, virt_base, len);
> +                       virt_base += len;
> +                       len = 0;
> +               } else {
> +                       while (len) {
> +                               if (chunk == 0) {
> +                                       scratch = ioread32(host->iobase +
> +                                                       AU6601_REG_BUFFER);
> +                                       chunk = 4;
> +                               }
> +
> +                               *buf = scratch & 0xFF;
> +
> +                               buf++;
> +                               scratch >>= 8;
> +                               chunk--;
> +                               len--;
> +                       }
> +               }
> +       }
> +
> +       sg_miter_stop(&host->sg_miter);
> +       local_irq_restore(flags);
> +}
> +
> +static void au6601_write_block(struct au6601_host *host)
> +{
> +       unsigned long flags;
> +       size_t blksize, len, chunk;
> +       void __iomem *virt_base = host->virt_base;
> +       u32 scratch;
> +       u8 *buf;
> +
> +       dev_dbg(host->dev, "PIO writing\n");
> +
> +       blksize = host->data->blksz * host->requested_blocks;
> +       chunk = 0;
> +       scratch = 0;
> +
> +       local_irq_save(flags);
> +
> +       while (blksize) {
> +               if (!sg_miter_next(&host->sg_miter))
> +                       BUG();
> +
> +               len = min(host->sg_miter.length, blksize);
> +
> +               blksize -= len;
> +               host->sg_miter.consumed = len;
> +
> +               buf = host->sg_miter.addr;
> +
> +               if (host->dma_on) {
> +                       memcpy_toio(virt_base, buf, len);
> +                       virt_base += len;
> +                       len = 0;
> +               } else {
> +                       while (len) {
> +                               scratch |= (u32)*buf << (chunk * 8);
> +
> +                               buf++;
> +                               chunk++;
> +                               len--;
> +
> +                               if ((chunk == 4) || ((len == 0)
> +                                               && (blksize == 0))) {
> +                                       iowrite32(scratch, host->iobase +
> +                                               AU6601_REG_BUFFER);
> +                                       chunk = 0;
> +                                       scratch = 0;
> +                               }
> +                       }
> +               }
> +       }
> +
> +       sg_miter_stop(&host->sg_miter);
> +
> +       local_irq_restore(flags);
> +}
> +
> +static void au6601_transfer_data(struct au6601_host *host)
> +{
> +       BUG_ON(!host->data);
> +
> +       if (host->blocks == 0)
> +               return;
> +
> +       if (host->data->flags & MMC_DATA_READ)
> +               au6601_read_block(host);
> +       else
> +               au6601_write_block(host);
> +
> +       host->blocks -= host->requested_blocks;
> +       if (host->dma_on) {
> +               host->dma_on = 0;
> +               if (host->blocks || (!host->blocks &&
> +                               (host->data->flags & MMC_DATA_WRITE)))
> +                       au6601_trigger_data_transfer(host, 1);
> +               else
> +                       au6601_finish_data(host);
> +       }
> +
> +       dev_dbg(host->dev, "PIO transfer complete.\n");
> +}
> +
> +static void au6601_finish_command(struct au6601_host *host)
> +{
> +       struct mmc_command *cmd = host->cmd;
> +
> +       BUG_ON(host->cmd == NULL);
> +
> +       if (host->cmd->flags & MMC_RSP_PRESENT) {
> +               cmd->resp[0] = ioread32be(host->iobase + AU6601_REG_CMD_RSP0);
> +               if (host->cmd->flags & MMC_RSP_136) {
> +                       cmd->resp[1] =
> +                               ioread32be(host->iobase + AU6601_REG_CMD_RSP1);
> +                       cmd->resp[2] =
> +                               ioread32be(host->iobase + AU6601_REG_CMD_RSP2);
> +                       cmd->resp[3] =
> +                               ioread32be(host->iobase + AU6601_REG_CMD_RSP3);
> +               }
> +
> +       }
> +
> +       host->cmd->error = 0;
> +
> +       /* Finished CMD23, now send actual command. */
> +       if (host->cmd == host->mrq->sbc) {
> +               host->cmd = NULL;
> +               au6601_send_cmd(host, host->mrq->cmd);
> +       } else {
> +               /* Processed actual command. */
> +               if (!host->data)
> +                       tasklet_schedule(&host->finish_tasklet);
> +               else if (host->data_early)
> +                       au6601_finish_data(host);
> +               else if (host->trigger_dma_dac) {
> +                       host->dma_on = 1;
> +                       au6601_transfer_data(host);
> +               }
> +
> +               host->cmd = NULL;
> +       }
> +}
> +
> +static void au6601_finish_data(struct au6601_host *host)
> +{
> +       struct mmc_data *data;
> +
> +       BUG_ON(!host->data);
> +
> +       data = host->data;
> +       host->data = NULL;
> +       host->dma_on = 0;
> +       host->trigger_dma_dac = 0;
> +
> +       /*
> +        * The specification states that the block count register must
> +        * be updated, but it does not specify at what point in the
> +        * data flow. That makes the register entirely useless to read
> +        * back so we have to assume that nothing made it to the card
> +        * in the event of an error.
> +        */
> +       if (data->error)
> +               data->bytes_xfered = 0;
> +       else
> +               data->bytes_xfered = data->blksz * data->blocks;
> +
> +       /*
> +        * Need to send CMD12 if -
> +        * a) open-ended multiblock transfer (no CMD23)
> +        * b) error in multiblock transfer
> +        */
> +       if (data->stop &&
> +           (data->error ||
> +            !host->mrq->sbc)) {
> +
> +               /*
> +                * The controller needs a reset of internal state machines
> +                * upon error conditions.
> +                */
> +               if (data->error) {
> +                       au6601_reset(host, AU6601_RESET_CMD);
> +                       au6601_reset(host, AU6601_RESET_DATA);
> +               }
> +               au6601_send_cmd(host, data->stop);
> +       } else
> +               tasklet_schedule(&host->finish_tasklet);
> +}
> +
> +static void au6601_prepare_sg_miter(struct au6601_host *host)
> +{
> +       unsigned int flags = SG_MITER_ATOMIC;
> +       struct mmc_data *data = host->data;
> +
> +       if (data->flags & MMC_DATA_READ)
> +               flags |= SG_MITER_TO_SG;
> +       else
> +               flags |= SG_MITER_FROM_SG;
> +       sg_miter_start(&host->sg_miter, data->sg, data->sg_len, flags);
> +}
> +
> +static void au6601_prepare_data(struct au6601_host *host,
> +                               struct mmc_command *cmd)
> +{
> +       unsigned int dma = 0;
> +       struct mmc_data *data = cmd->data;
> +
> +       WARN_ON(host->data);
> +
> +       if (!data)
> +               return;
> +
> +       /* Sanity checks */
> +       BUG_ON(data->blksz * data->blocks > 524288);
> +       BUG_ON(data->blksz > host->mmc->max_blk_size);
> +       BUG_ON(data->blocks > AU6601_MAX_BLOCK_COUNT);
> +
> +       host->data = data;
> +       host->data_early = 0;
> +       host->data->bytes_xfered = 0;
> +       host->requested_blocks = 1;
> +
> +       au6601_prepare_sg_miter(host);
> +       host->blocks = data->blocks;
> +
> +       if (!disable_dma &&
> +                       host->blocks > 1 &&
> +                       data->blksz == host->mmc->max_blk_size) {
> +               dma = 1;
> +
> +               if (data->flags & MMC_DATA_WRITE) {
> +                       /* prepare first write buffer */
> +                       /* Don't trigger data transfer now.
> +                        * DMA may start it too eraly */
> +                       host->trigger_dma_dac = 1;
> +                       return;
> +               }
> +       }
> +
> +       au6601_trigger_data_transfer(host, dma);
> +}
> +
> +static void au6601_send_cmd(struct au6601_host *host,
> +                           struct mmc_command *cmd)
> +{
> +       u8 ctrl; /*some mysterious flags and control */
> +       unsigned long timeout;
> +
> +       timeout = jiffies;
> +       if (!cmd->data && cmd->busy_timeout > 9000)
> +               timeout += DIV_ROUND_UP(cmd->busy_timeout, 1000) * HZ + HZ;
> +       else
> +               timeout += 10 * HZ;
> +       mod_timer(&host->timer, timeout);
> +
> +       host->cmd = cmd;
> +       au6601_prepare_data(host, cmd);
> +
> +       iowrite8(cmd->opcode | 0x40, host->iobase + AU6601_REG_CMD_OPCODE);
> +       iowrite32be(cmd->arg, host->iobase + AU6601_REG_CMD_ARG);
> +
> +       switch (mmc_resp_type(cmd)) {
> +       case MMC_RSP_NONE:
> +               ctrl = 0;
> +               break;
> +       case MMC_RSP_R1:
> +               ctrl = 0x40;
> +               break;
> +       case MMC_RSP_R1B:
> +               ctrl = 0x40 | 0x10;
> +               break;
> +       case MMC_RSP_R2:
> +               ctrl = 0xc0;
> +               break;
> +       case MMC_RSP_PRESENT | MMC_RSP_OPCODE:
> +       case MMC_RSP_R3:
> +               ctrl = 0x80;
> +               break;
> +       default:
> +               dev_err(host->dev, "%s: cmd->flag (0x%02x) is not valid\n",
> +                       mmc_hostname(host->mmc), mmc_resp_type(cmd));
> +               break;
> +       }
> +
> +       iowrite8(ctrl | 0x20, host->iobase + AU6601_REG_CMD_CTRL);
> +}
> +
> +/*****************************************************************************\
> + *                                                                          *
> + * Interrupt handling                                                       *
> + *                                                                          *
> +\*****************************************************************************/
> +
> +static void au6601_cmd_irq(struct au6601_host *host, u32 intmask)
> +{
> +       BUG_ON(intmask == 0);
> +
> +       if (!host->cmd) {
> +               dev_err(host->dev,
> +                       "Got command interrupt 0x%08x even though no command operation was in progress.\n",
> +                       intmask);
> +               return;
> +       }
> +
> +       if (intmask & AU6601_INT_TIMEOUT)
> +               host->cmd->error = -ETIMEDOUT;
> +       else if (intmask & (AU6601_INT_CRC | AU6601_INT_END_BIT |
> +                       AU6601_INT_INDEX))
> +               host->cmd->error = -EILSEQ;
> +
> +       if (host->cmd->error) {
> +               tasklet_schedule(&host->finish_tasklet);
> +               return;
> +       }
> +
> +       /*
> +        * The host can send and interrupt when the busy state has
> +        * ended, allowing us to wait without wasting CPU cycles.
> +        * Unfortunately this is overloaded on the "data complete"
> +        * interrupt, so we need to take some care when handling
> +        * it.
> +        *
> +        * Note: The 1.0 specification is a bit ambiguous about this
> +        *       feature so there might be some problems with older
> +        *       controllers.
> +        */
> +       if (host->cmd->flags & MMC_RSP_BUSY) {
> +               if (host->cmd->data)
> +                       dev_warn(host->dev,
> +                                "Cannot wait for busy signal when also doing a data transfer");
> +       }
> +
> +       if (intmask & AU6601_INT_RESPONSE)
> +               au6601_finish_command(host);
> +}
> +
> +static void au6601_data_irq(struct au6601_host *host, u32 intmask)
> +{
> +       BUG_ON(intmask == 0);
> +
> +       if (!host->data) {
> +               /* FIXME: Ist is same for AU6601
> +                * The "data complete" interrupt is also used to
> +                * indicate that a busy state has ended. See comment
> +                * above in au6601_cmd_irq().
> +                */
> +               if (host->cmd && (host->cmd->flags & MMC_RSP_BUSY)) {
> +                       if (intmask & AU6601_INT_DATA_END) {
> +                               au6601_finish_command(host);
> +                               return;
> +                       }
> +               }
> +
> +               dev_err(host->dev,
> +                       "Got data interrupt 0x%08x even though no data operation was in progress.\n",
> +                       (unsigned)intmask);
> +
> +               if (intmask & AU6601_INT_ERROR_MASK) {
> +                       host->cmd->error = -ETIMEDOUT;
> +                       tasklet_schedule(&host->finish_tasklet);
> +               }
> +               return;
> +       }
> +
> +       if (intmask & AU6601_INT_DATA_TIMEOUT)
> +               host->data->error = -ETIMEDOUT;
> +       else if (intmask & AU6601_INT_DATA_END_BIT)
> +               host->data->error = -EILSEQ;
> +       else if (intmask & AU6601_INT_DATA_CRC)
> +               host->data->error = -EILSEQ;
> +
> +       if (host->data->error)
> +               au6601_finish_data(host);
> +       else {
> +               if (intmask & (AU6601_INT_DATA_AVAIL | AU6601_INT_SPACE_AVAIL))
> +                       au6601_transfer_data(host);
> +
> +               if (intmask & AU6601_INT_DATA_END) {
> +                       if (host->cmd) {
> +                               /*
> +                                * Data managed to finish before the
> +                                * command completed. Make sure we do
> +                                * things in the proper order.
> +                                */
> +                               host->data_early = 1;
> +                       } else if (host->blocks && !host->dma_on) {
> +                               /*
> +                                * Probably we do multi block operation.
> +                                * Prepare PIO for next block.
> +                                */
> +                               au6601_trigger_data_transfer(host, 0);
> +                       } else if (host->blocks && host->dma_on) {
> +                               au6601_transfer_data(host);
> +                       } else {
> +                               if (host->dma_on)
> +                                       au6601_transfer_data(host);
> +                               au6601_finish_data(host);
> +                       }
> +               }
> +       }
> +}
> +
> +static irqreturn_t au6601_irq(int irq, void *d)
> +{
> +       struct au6601_host *host = d;
> +       irqreturn_t ret = IRQ_HANDLED;
> +       u32 intmask;
> +
> +       spin_lock(&host->lock);
> +
> +       intmask = ioread32(host->iobase + AU6601_REG_INT_STATUS);
> +       iowrite32(intmask, host->iobase + AU6601_REG_INT_STATUS);
> +
> +       /* some thing bad */
> +       if (unlikely(!intmask || intmask == AU6601_INT_ALL_MASK)) {
> +               dev_warn(host->dev, "impossible IRQ %x\n", intmask);
> +               ret = IRQ_NONE;
> +               goto exit;
> +       }
> +
> +       if (intmask & AU6601_INT_CMD_MASK) {
> +               dev_dbg(host->dev, "CMD IRQ %x\n", intmask);
> +
> +               au6601_cmd_irq(host, intmask & AU6601_INT_CMD_MASK);
> +               intmask &= ~AU6601_INT_CMD_MASK;
> +       }
> +
> +       if (intmask & AU6601_INT_DATA_MASK) {
> +               dev_dbg(host->dev, "DATA IRQ %x\n", intmask);
> +               au6601_data_irq(host, intmask & AU6601_INT_DATA_MASK);
> +               intmask &= ~AU6601_INT_DATA_MASK;
> +       }
> +
> +       if (intmask & (AU6601_INT_CARD_INSERT | AU6601_INT_CARD_REMOVE)) {
> +               /* this check can be remove */
> +               if (intmask & AU6601_INT_CARD_REMOVE)
> +                       dev_dbg(host->dev, "card removed\n");
> +               else
> +                       dev_dbg(host->dev, "card inserted\n");
> +
> +               intmask &= ~(AU6601_INT_CARD_INSERT | AU6601_INT_CARD_REMOVE);
> +               tasklet_schedule(&host->card_tasklet);
> +       }
> +
> +       if (intmask & 0x100) {
> +               dev_warn(host->dev,
> +                        "0x100 (card INT?) got unknown IRQ with %x\n",
> +                        intmask);
> +               intmask &= ~0x100;
> +       }
> +
> +       if (intmask & 0xFFFF7FFF) {
> +               dev_warn(host->dev, "0xFFFF7FFF got unhandled IRQ with %x\n",
> +                        intmask);
> +       }
> +
> +exit:
> +       spin_unlock(&host->lock);
> +       return ret;
> +}
> +
> +static void au6601_sdc_request(struct mmc_host *mmc, struct mmc_request *mrq)
> +{
> +       struct au6601_host *host;
> +       unsigned long flags;
> +
> +       host = mmc_priv(mmc);
> +       spin_lock_irqsave(&host->lock, flags);
> +
> +       host->mrq = mrq;
> +
> +       /* check if card is present then send command and data */
> +       if (ioread8(host->iobase + REG_76) & 0x1)
> +               au6601_send_cmd(host, mrq->cmd);
> +       else {
> +               mrq->cmd->error = -ENOMEDIUM;
> +               tasklet_schedule(&host->finish_tasklet);
> +       }
> +
> +       spin_unlock_irqrestore(&host->lock, flags);
> +}
> +
> +static void au6601_set_clock(struct au6601_host *host, unsigned int clock)
> +{
> +       unsigned int div = 0, mult = 0, ctrl = 0x1;
> +
> +       /* FIXME: mesuered and calculated values are different.
> +        * the clock is unstable in some mult/div combinations.
> +        */
> +       if (clock >= MHZ_TO_HZ(208)) {
> +               mult = 0xb0;    /* 30 * ? / 2 = ?MHz */
> +               div = 2;
> +       } else if (clock >= MHZ_TO_HZ(194)) {
> +               mult = 0x30;    /* 30 * 14 / 2 = 210MHz */
> +               div = 2;
> +       } else if (clock >= MHZ_TO_HZ(130)) {
> +               mult = 0x30;    /* 30 * 14 / 3 = 140MHz */
> +               div = 3;
> +       } else if (clock >= MHZ_TO_HZ(100)) {
> +               mult = 0x30;    /* 30 * 14 / 4 = 105MHz */
> +               div = 4;
> +       } else if (clock >= MHZ_TO_HZ(80)) {
> +               mult = 0x30;    /* 30 * 14 / 5 = 84MHz */
> +               div = 5;
> +       } else if (clock >= MHZ_TO_HZ(60)) {
> +               mult = 0x30;    /* 30 * 14 / 7 = 60MHz */
> +               div = 7;
> +       } else if (clock >= MHZ_TO_HZ(50)) {
> +               mult = 0x10;    /* 30 * 2 / 1 = 60MHz */
> +               div = 1;
> +       } else if (clock >= MHZ_TO_HZ(40)) {
> +               mult = 0x30;    /* 30 * 14 / 10 = 42MHz */
> +               div = 10;
> +       } else if (clock >= MHZ_TO_HZ(25)) {
> +               mult = 0x10;    /* 30 * 2 / 2 = 30MHz */
> +               div = 2;
> +       } else if (clock >= MHZ_TO_HZ(20)) {
> +               mult = 0x20;    /* 30 * 4 / 7 = 17MHz */
> +               div = 7;
> +       } else if (clock >= MHZ_TO_HZ(10)) {
> +               mult = 0x10;    /* 30 * 2 / 5 = 12MHz */
> +               div = 5;
> +       } else if (clock >= MHZ_TO_HZ(5)) {
> +               mult = 0x10;    /* 30 * 2 / 10 = 6MHz */
> +               div = 10;
> +       } else if (clock >= MHZ_TO_HZ(1)) {
> +               mult = 0x0;     /* 30 / 16 = 1,8 MHz */
> +               div = 16;
> +       } else if (clock == 0) {
> +               ctrl = 0;
> +       } else {
> +               mult = 0x0;     /* reversed 150 * 200 = 30MHz */
> +               div = 200;      /* 150 KHZ mesured */
> +       }
> +       dev_dbg(host->dev, "set freq %d, %x, %x\n", clock, div, mult);
> +       iowrite16((div - 1) << 8 | mult | ctrl,
> +                 host->iobase + AU6601_REG_PLL_CTRL);
> +}
> +
> +static void au6601_sdc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
> +{
> +       struct au6601_host *host;
> +       unsigned long flags;
> +
> +       host = mmc_priv(mmc);
> +       spin_lock_irqsave(&host->lock, flags);
> +
> +       iowrite8(0, host->iobase + REG_85);
> +       iowrite8(0x31, host->iobase + REG_7B);
> +       iowrite8(0x33, host->iobase + REG_7C);
> +       iowrite8(1, host->iobase + REG_75);
> +       iowrite8(0, host->iobase + REG_85);
> +
> +       if (ios->bus_width == MMC_BUS_WIDTH_1) {
> +               iowrite8(0x0,
> +                        host->iobase + AU6601_REG_BUS_CTRL);
> +               au6601_clear_set_reg86(host, 0xc0, 0);
> +       } else if (ios->bus_width == MMC_BUS_WIDTH_4) {
> +               iowrite8(AU6601_BUS_WIDTH_4BIT,
> +                        host->iobase + AU6601_REG_BUS_CTRL);
> +               au6601_clear_set_reg86(host, 0, 0xc0);
> +       } else
> +               dev_err(host->dev, "Unknown BUS mode\n");
> +
> +       au6601_set_clock(host, ios->clock);
> +
> +       switch (ios->power_mode) {
> +       case MMC_POWER_OFF:
> +               au6601_set_power(host, 0x1 | 0x8, 0);
> +               break;
> +       case MMC_POWER_UP:
> +               au6601_set_power(host, 0x8, 1);
> +               break;
> +       case MMC_POWER_ON:
> +               au6601_set_power(host, 0x1, 1);
> +               au6601_set_power(host, 0x8, 0);
> +               break;
> +       default:
> +               dev_err(host->dev, "Unknown power parametr\n");
> +       }
> +
> +       iowrite8(0x80, host->iobase + REG_83);
> +       iowrite8(0x7d, host->iobase + REG_69);
> +       ioread8(host->iobase + REG_74);
> +       spin_unlock_irqrestore(&host->lock, flags);
> +}
> +
> +static int au6601_ops_card_busy(struct mmc_host *mmc)
> +{
> +       struct au6601_host *host;
> +       host = mmc_priv(mmc);
> +
> +       return au6601_card_busy(host);
> +}
> +
> +static const struct mmc_host_ops au6601_sdc_ops = {
> +       .request = au6601_sdc_request,
> +       .set_ios = au6601_sdc_set_ios,
> +
> +       .card_busy = au6601_ops_card_busy,
> +};
> +
> +/*****************************************************************************\
> + *                                                                          *
> + * Tasklets                                                                 *
> + *                                                                          *
> +\*****************************************************************************/
> +
> +static void au6601_tasklet_card(unsigned long param)
> +{
> +       struct au6601_host *host = (struct au6601_host *)param;
> +
> +       mmc_detect_change(host->mmc, msecs_to_jiffies(200));
> +}
> +
> +static void au6601_tasklet_finish(unsigned long param)
> +{
> +       struct au6601_host *host;
> +       unsigned long flags;
> +       struct mmc_request *mrq;
> +
> +       host = (struct au6601_host *)param;
> +
> +       spin_lock_irqsave(&host->lock, flags);
> +
> +       /*
> +        * If this tasklet gets rescheduled while running, it will
> +        * be run again afterwards but without any active request.
> +        */
> +       if (!host->mrq) {
> +               spin_unlock_irqrestore(&host->lock, flags);
> +               return;
> +       }
> +
> +       del_timer(&host->timer);
> +
> +       mrq = host->mrq;
> +
> +       /*
> +        * The controller needs a reset of internal state machines
> +        * upon error conditions.
> +        */
> +       if ((mrq->cmd && mrq->cmd->error) ||
> +                (mrq->data && (mrq->data->error ||
> +                 (mrq->data->stop && mrq->data->stop->error)))) {
> +
> +               au6601_reset(host, AU6601_RESET_CMD);
> +               au6601_reset(host, AU6601_RESET_DATA);
> +       }
> +
> +       host->mrq = NULL;
> +       host->cmd = NULL;
> +       host->data = NULL;
> +       host->dma_on = 0;
> +       host->trigger_dma_dac = 0;
> +
> +       spin_unlock_irqrestore(&host->lock, flags);
> +
> +       mmc_request_done(host->mmc, mrq);
> +}
> +
> +static void au6601_timeout_timer(unsigned long data)

I am just a bit curious, does this controller support hardware busy
detection on DAT1 line while waiting for command completion?

> +{
> +       struct au6601_host *host;
> +       unsigned long flags;
> +
> +       host = (struct au6601_host *)data;
> +
> +       spin_lock_irqsave(&host->lock, flags);
> +
> +       if (host->mrq) {
> +               dev_err(host->dev,
> +                       "Timeout waiting for hardware interrupt.\n");
> +
> +               if (host->data) {
> +                       host->data->error = -ETIMEDOUT;
> +                       au6601_finish_data(host);
> +               } else {
> +                       if (host->cmd)
> +                               host->cmd->error = -ETIMEDOUT;
> +                       else
> +                               host->mrq->cmd->error = -ETIMEDOUT;
> +
> +                       tasklet_schedule(&host->finish_tasklet);
> +               }
> +       }
> +
> +       mmiowb();
> +       spin_unlock_irqrestore(&host->lock, flags);
> +}
> +
> +
> +
> +static void au6601_init_mmc(struct au6601_host *host)
> +{
> +       struct mmc_host *mmc = host->mmc;
> +
> +       mmc->f_min = AU6601_MIN_CLOCK;
> +       mmc->f_max = AU6601_MAX_CLOCK;
> +       mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
> +       mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SD_HIGHSPEED;
> +       mmc->ops = &au6601_sdc_ops;
> +
> +       /* Hardware cannot do scatter lists? */
> +       mmc->max_segs = AU6601_MAX_SEGMENTS;
> +
> +       mmc->max_blk_size = AU6601_MAX_BLOCK_LENGTH;
> +       mmc->max_blk_count = AU6601_MAX_BLOCK_COUNT;
> +
> +       mmc->max_seg_size = AU6601_MAX_BLOCK_LENGTH * AU6601_MAX_DMA_BLOCKS;
> +       mmc->max_req_size = mmc->max_seg_size * mmc->max_segs;
> +}
> +
> +static void au6601_hw_init(struct au6601_host *host)
> +{
> +
> +       iowrite8(0, host->iobase + REG_74);
> +
> +       iowrite8(0, host->iobase + REG_76);
> +       /* disable DlinkMode? disabled by default. */
> +       iowrite8(0x80, host->iobase + REG_76);
> +
> +       au6601_reset(host, AU6601_RESET_CMD);
> +
> +       iowrite8(0x0, host->iobase + REG_05);
> +       iowrite8(0x1, host->iobase + REG_75);
> +       au6601_clear_set_irqs(host, AU6601_INT_ALL_MASK,
> +               AU6601_INT_CMD_MASK | AU6601_INT_DATA_MASK |
> +               AU6601_INT_CARD_INSERT | AU6601_INT_CARD_REMOVE |
> +               AU6601_INT_CARD_INT | AU6601_INT_BUS_POWER);
> +       iowrite32(0x0, host->iobase + AU6601_REG_BUS_CTRL);
> +
> +       au6601_reset(host, AU6601_RESET_DATA);
> +
> +       iowrite8(0x0, host->iobase + REG_05);
> +       iowrite8(0x0, host->iobase + REG_85);
> +       iowrite8(0x8, host->iobase + REG_75);
> +       iowrite32(0x3d00fa, host->iobase + REG_B4);
> +
> +       au6601_set_power(host, 0x1, 0);
> +       au6601_set_power(host, 0x8, 0);
> +
> +       host->dma_on = 0;
> +}
> +
> +static int __init au6601_pci_probe(struct pci_dev *pdev,
> +                          const struct pci_device_id *ent)
> +{
> +       struct mmc_host *mmc;
> +       struct au6601_host *host;
> +       int ret, bar;
> +
> +       BUG_ON(pdev == NULL);
> +       BUG_ON(ent == NULL);
> +
> +       dev_info(&pdev->dev, "AU6601 controller found [%04x:%04x] (rev %x)\n",
> +                (int)pdev->vendor, (int)pdev->device, (int)pdev->revision);
> +
> +       if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM)) {
> +               dev_err(&pdev->dev, "BAR %d is not iomem. Aborting.\n", bar);
> +               return -ENODEV;
> +       }
> +
> +
> +       ret = pcim_enable_device(pdev);
> +       if (ret)
> +               return ret;
> +
> +       /* FIXME: create managed version of mmc_alloc_host and use it */
> +       mmc = mmc_alloc_host(sizeof(struct au6601_host *), &pdev->dev);
> +       if (!mmc) {
> +               dev_err(&pdev->dev, "Can't allocate MMC\n");
> +               return -ENOMEM;
> +       }
> +
> +       host = mmc_priv(mmc);
> +       host->mmc = mmc;
> +       host->pdev = pdev;
> +       host->dev = &pdev->dev;
> +
> +       ret = pci_request_region(pdev, bar, DRVNAME);
> +       if (ret) {
> +               dev_err(&pdev->dev, "Cannot request region\n");
> +               return -ENOMEM;
> +       }
> +
> +       host->iobase = pcim_iomap(pdev, bar, 0);
> +       if (!host->iobase)
> +               return -ENOMEM;
> +
> +       ret = devm_request_irq(&pdev->dev, pdev->irq, au6601_irq,
> +                               IRQF_TRIGGER_FALLING, "au6601 host",
> +                               host);
> +
> +       if (ret) {
> +               dev_err(&pdev->dev, "Failed to get irq for data line\n");
> +               return -ENOMEM;
> +       }
> +
> +       host->virt_base = dmam_alloc_coherent(&pdev->dev,
> +               AU6601_MAX_BLOCK_LENGTH * AU6601_MAX_DMA_BLOCKS,
> +               &host->phys_base, GFP_KERNEL);
> +       if (!host->virt_base) {
> +               dev_err(&pdev->dev, "Failed to alloc DMA\n");
> +               return -ENOMEM;
> +       }
> +
> +       ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));
> +       if (ret) {
> +               dev_err(&pdev->dev, "Failed to set DMA mask\n");
> +               return ret;
> +       }
> +
> +       pci_set_master(pdev);
> +       pci_set_drvdata(pdev, host);
> +
> +       spin_lock_init(&host->lock);
> +       /*
> +        * Init tasklets.
> +        */
> +       tasklet_init(&host->card_tasklet,
> +               au6601_tasklet_card, (unsigned long)host);
> +       tasklet_init(&host->finish_tasklet,
> +               au6601_tasklet_finish, (unsigned long)host);
> +       setup_timer(&host->timer, au6601_timeout_timer, (unsigned long)host);
> +
> +       au6601_init_mmc(host);
> +       au6601_hw_init(host);
> +
> +       mmc_add_host(mmc);
> +       return 0;
> +}
> +
> +static void au6601_hw_uninit(struct au6601_host *host)
> +{
> +       iowrite8(0x0, host->iobase + REG_76);
> +       au6601_clear_set_irqs(host, AU6601_INT_ALL_MASK, 0);
> +
> +       au6601_set_power(host, 0x1, 0);
> +
> +       iowrite8(0x0, host->iobase + REG_85);
> +       iowrite8(0x0, host->iobase + REG_B4);
> +
> +       au6601_set_power(host, 0x8, 0);
> +}
> +
> +static void __exit au6601_pci_remove(struct pci_dev *pdev)
> +{
> +       struct au6601_host *host;
> +
> +       host = pci_get_drvdata(pdev);
> +
> +       au6601_hw_uninit(host);
> +
> +       del_timer_sync(&host->timer);
> +       tasklet_kill(&host->card_tasklet);
> +       tasklet_kill(&host->finish_tasklet);
> +
> +       mmc_remove_host(host->mmc);
> +       mmc_free_host(host->mmc);
> +}
> +
> +#ifdef CONFIG_PM

->CONFIG_PM_SLEEP

> +
> +static int au6601_suspend(struct pci_dev *pdev, pm_message_t state)
> +{
> +       struct au6601_host *host;
> +       host = pci_get_drvdata(pdev);
> +
> +       au6601_hw_uninit(host);
> +
> +       pci_save_state(pdev);
> +       pci_enable_wake(pdev, pci_choose_state(pdev, state), 0);
> +       pci_disable_device(pdev);
> +       pci_set_power_state(pdev, pci_choose_state(pdev, state));
> +
> +       return 0;
> +}
> +
> +static int au6601_resume(struct pci_dev *pdev)
> +{
> +       struct au6601_host *host;
> +       int ret;
> +
> +       host = pci_get_drvdata(pdev);
> +
> +       pci_set_power_state(pdev, PCI_D0);
> +       pci_restore_state(pdev);
> +       ret = pci_enable_device(pdev);
> +       if (ret)
> +               return ret;
> +
> +       au6601_hw_init(host);
> +       return 0;
> +}
> +
> +
> +#else /* CONFIG_PM */
> +
> +#define au6601_suspend NULL
> +#define au6601_resume NULL
> +
> +#endif /* CONFIG_PM */

Instead of the above tricks, can't you use the dev_pm_ops instead of
the PM callbacks in the driver? And the use the "SIMPLE_DEV_PM_OPS"
macro?

> +
> +static struct pci_driver au6601_driver = {
> +       .name =  DRVNAME,
> +       .id_table =     pci_ids,
> +       .probe =        au6601_pci_probe,
> +       .remove =       au6601_pci_remove,
> +       .suspend = au6601_suspend,
> +       .resume = au6601_resume,
> +};
> +
> +module_pci_driver(au6601_driver);
> +
> +module_param(disable_dma, bool, S_IRUGO);
> +MODULE_PARM_DESC(disable_dma, "Disable DMA");
> +
> +MODULE_AUTHOR("Oleksij Rempel <linux@xxxxxxxxxxxxxxxx>");
> +MODULE_DESCRIPTION("PCI driver for Alcor Micro AU6601 Secure Digital Host Controller Interface");
> +MODULE_LICENSE("GPL");
> --
> 1.9.1
>

Kind regards
Uffe

> --
> To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
> the body of a message to majordomo@xxxxxxxxxxxxxxx
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux USB Devel]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux