Am 14.05.2014 09:19, schrieb Oleksij Rempel: > Hello all, > > are there any comments, suggestions, critics about this patch? Repost. Any comments NACK/ACK? Any one alive? > Am 08.05.2014 11:58, schrieb Oleksij Rempel: >> 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; >> + >> +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; >> + >> + 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); >> + WARN_ON_ONCE(host->dma_on == 1); >> + >> + 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) >> +{ >> + 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 >> + >> +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 */ >> + >> +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"); >> > > -- Regards, Oleksij
Attachment:
signature.asc
Description: OpenPGP digital signature