/* * linux/drivers/mmc/sdhci.c - Secure Digital Host Controller Interface driver * * Copyright (C) 2005-2006 Pierre Ossman, 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. */ /* * Note that PIO transfer is rather crappy atm. The buffer full/empty * interrupts aren't reliable so we currently transfer the entire buffer * directly. Patches to solve the problem are welcome. */ #include <linux/delay.h> #include <linux/highmem.h> #include <linux/pci.h> #include <linux/dma-mapping.h> #include <linux/mmc/host.h> #include <linux/mmc/protocol.h> #include <asm/scatterlist.h> #include "sdhci.h" #define DRIVER_NAME "sdhci" #define DRIVER_VERSION "0.11" #define BUGMAIL "<sdhci-devel@list.drzeus.cx>" #define DBG(f, x...) \ pr_debug(DRIVER_NAME " [%s()]: " f, __func__,## x) static const struct pci_device_id pci_ids[] __devinitdata = { /* handle any SD host controller */ {PCI_DEVICE_CLASS((PCI_CLASS_SYSTEM_SDHCI << 8), 0xFFFF00)}, { /* end: all zeroes */ }, }; MODULE_DEVICE_TABLE(pci, pci_ids); static void sdhci_prepare_data(struct sdhci_host *, struct mmc_data *); static void sdhci_finish_data(struct sdhci_host *); static void sdhci_send_command(struct sdhci_host *, struct mmc_command *); static void sdhci_finish_command(struct sdhci_host *); static void sdhci_dumpregs(struct sdhci_host *host) { printk(KERN_DEBUG DRIVER_NAME ": ============== REGISTER DUMP ==============\n"); printk(KERN_DEBUG DRIVER_NAME ": Sys addr: 0x%08x | Version: 0x%08x\n", readl(host->ioaddr + SDHCI_DMA_ADDRESS), readw(host->ioaddr + SDHCI_HOST_VERSION)); printk(KERN_DEBUG DRIVER_NAME ": Blk size: 0x%08x | Blk cnt: 0x%08x\n", readw(host->ioaddr + SDHCI_BLOCK_SIZE), readw(host->ioaddr + SDHCI_BLOCK_COUNT)); printk(KERN_DEBUG DRIVER_NAME ": Argument: 0x%08x | Trn mode: 0x%08x\n", readl(host->ioaddr + SDHCI_ARGUMENT), readw(host->ioaddr + SDHCI_TRANSFER_MODE)); printk(KERN_DEBUG DRIVER_NAME ": Present: 0x%08x | Host ctl: 0x%08x\n", readl(host->ioaddr + SDHCI_PRESENT_STATE), readb(host->ioaddr + SDHCI_HOST_CONTROL)); printk(KERN_DEBUG DRIVER_NAME ": Power: 0x%08x | Blk gap: 0x%08x\n", readb(host->ioaddr + SDHCI_POWER_CONTROL), readb(host->ioaddr + SDHCI_BLOCK_GAP_CONTROL)); printk(KERN_DEBUG DRIVER_NAME ": Wake-up: 0x%08x | Clock: 0x%08x\n", readb(host->ioaddr + SDHCI_WALK_UP_CONTROL), readw(host->ioaddr + SDHCI_CLOCK_CONTROL)); printk(KERN_DEBUG DRIVER_NAME ": Timeout: 0x%08x | Int stat: 0x%08x\n", readb(host->ioaddr + SDHCI_TIMEOUT_CONTROL), readl(host->ioaddr + SDHCI_INT_STATUS)); printk(KERN_DEBUG DRIVER_NAME ": Int enab: 0x%08x | Sig enab: 0x%08x\n", readl(host->ioaddr + SDHCI_INT_ENABLE), readl(host->ioaddr + SDHCI_SIGNAL_ENABLE)); printk(KERN_DEBUG DRIVER_NAME ": AC12 err: 0x%08x | Slot int: 0x%08x\n", readw(host->ioaddr + SDHCI_ACMD12_ERR), readw(host->ioaddr + SDHCI_SLOT_INT_STATUS)); printk(KERN_DEBUG DRIVER_NAME ": Caps: 0x%08x | Max curr: 0x%08x\n", readl(host->ioaddr + SDHCI_CAPABILITIES), readl(host->ioaddr + SDHCI_MAX_CURRENT)); printk(KERN_DEBUG DRIVER_NAME ": ===========================================\n"); } /*****************************************************************************\ * * * Low level functions * * * \*****************************************************************************/ static void sdhci_reset(struct sdhci_host *host, u8 mask) { writeb(mask, host->ioaddr + SDHCI_SOFTWARE_RESET); if (mask & SDHCI_RESET_ALL) { host->clock = 0; mdelay(50); } } static void sdhci_init(struct sdhci_host *host) { u32 intmask; sdhci_reset(host, SDHCI_RESET_ALL); intmask = ~(SDHCI_INT_CARD_INT | SDHCI_INT_BUF_EMPTY | SDHCI_INT_BUF_FULL); writel(intmask, host->ioaddr + SDHCI_INT_ENABLE); writel(intmask, host->ioaddr + SDHCI_SIGNAL_ENABLE); /* This is unknown magic. */ writeb(0xE, host->ioaddr + SDHCI_TIMEOUT_CONTROL); } static void sdhci_activate_led(struct sdhci_host *host) { u8 ctrl; ctrl = readb(host->ioaddr + SDHCI_HOST_CONTROL); ctrl |= SDHCI_CTRL_LED; writeb(ctrl, host->ioaddr + SDHCI_HOST_CONTROL); } static void sdhci_deactivate_led(struct sdhci_host *host) { u8 ctrl; ctrl = readb(host->ioaddr + SDHCI_HOST_CONTROL); ctrl &= ~SDHCI_CTRL_LED; writeb(ctrl, host->ioaddr + SDHCI_HOST_CONTROL); } /*****************************************************************************\ * * * Core functions * * * \*****************************************************************************/ static inline char* sdhci_kmap_sg(struct sdhci_host* host) { host->mapped_sg = kmap_atomic(host->cur_sg->page, KM_BIO_SRC_IRQ); return host->mapped_sg + host->cur_sg->offset; } static inline void sdhci_kunmap_sg(struct sdhci_host* host) { kunmap_atomic(host->mapped_sg, KM_BIO_SRC_IRQ); } static inline int sdhci_next_sg(struct sdhci_host* host) { /* * Skip to next SG entry. */ host->cur_sg++; host->num_sg--; /* * Any entries left? */ if (host->num_sg > 0) { host->offset = 0; host->remain = host->cur_sg->length; } return host->num_sg; } static void sdhci_transfer_pio(struct sdhci_host *host) { char *buffer; u32 mask; int bytes, size; unsigned long max_jiffies; BUG_ON(!host->data); if (host->num_sg == 0) return; bytes = 0; if (host->data->flags & MMC_DATA_READ) mask = SDHCI_DATA_AVAILABLE; else mask = SDHCI_SPACE_AVAILABLE; buffer = sdhci_kmap_sg(host) + host->offset; /* Transfer shouldn't take more than 5 s */ max_jiffies = jiffies + HZ * 5; while (host->size > 0) { if (time_after(jiffies, max_jiffies)) { printk(KERN_ERR "%s: PIO transfer stalled. " "Please report this to " BUGMAIL ".\n", mmc_hostname(host->mmc)); sdhci_dumpregs(host); sdhci_kunmap_sg(host); host->data->error = MMC_ERR_FAILED; sdhci_finish_data(host); return; } if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & mask)) continue; size = min(host->size, host->remain); if (size >= 4) { if (host->data->flags & MMC_DATA_READ) *(u32*)buffer = readl(host->ioaddr + SDHCI_BUFFER); else writel(*(u32*)buffer, host->ioaddr + SDHCI_BUFFER); size = 4; } else if (size >= 2) { if (host->data->flags & MMC_DATA_READ) *(u16*)buffer = readw(host->ioaddr + SDHCI_BUFFER); else writew(*(u16*)buffer, host->ioaddr + SDHCI_BUFFER); size = 2; } else { if (host->data->flags & MMC_DATA_READ) *(u8*)buffer = readb(host->ioaddr + SDHCI_BUFFER); else writeb(*(u8*)buffer, host->ioaddr + SDHCI_BUFFER); size = 1; } buffer += size; host->offset += size; host->remain -= size; bytes += size; host->size -= size; if (host->remain == 0) { sdhci_kunmap_sg(host); if (sdhci_next_sg(host) == 0) { DBG("PIO transfer: %d bytes\n", bytes); return; } buffer = sdhci_kmap_sg(host); } } sdhci_kunmap_sg(host); DBG("PIO transfer: %d bytes\n", bytes); } static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_data *data) { u16 mode; WARN_ON(host->data); if (data == NULL) { writew(0, host->ioaddr + SDHCI_TRANSFER_MODE); return; } DBG("blksz %04x blks %04x flags %08x\n", data->blksz, data->blocks, data->flags); DBG("tsac %d ms nsac %d clk\n", data->timeout_ns / 1000000, data->timeout_clks); mode = SDHCI_TRNS_BLK_CNT_EN; if (data->blocks > 1) mode |= SDHCI_TRNS_MULTI; if (data->flags & MMC_DATA_READ) mode |= SDHCI_TRNS_READ; if (host->flags & SDHCI_USE_DMA) mode |= SDHCI_TRNS_DMA; writew(mode, host->ioaddr + SDHCI_TRANSFER_MODE); writew(data->blksz, host->ioaddr + SDHCI_BLOCK_SIZE); writew(data->blocks, host->ioaddr + SDHCI_BLOCK_COUNT); if (host->flags & SDHCI_USE_DMA) { int count; count = pci_map_sg(host->chip->pdev, data->sg, data->sg_len, (data->flags & MMC_DATA_READ)?PCI_DMA_FROMDEVICE:PCI_DMA_TODEVICE); BUG_ON(count != 1); writel(sg_dma_address(data->sg), host->ioaddr + SDHCI_DMA_ADDRESS); } else { host->size = data->blksz * data->blocks; host->cur_sg = data->sg; host->num_sg = data->sg_len; host->offset = 0; host->remain = host->cur_sg->length; } } static void sdhci_finish_data(struct sdhci_host *host) { struct mmc_data *data; u32 intmask; u16 blocks; BUG_ON(!host->data); data = host->data; host->data = NULL; if (host->flags & SDHCI_USE_DMA) { pci_unmap_sg(host->chip->pdev, data->sg, data->sg_len, (data->flags & MMC_DATA_READ)?PCI_DMA_FROMDEVICE:PCI_DMA_TODEVICE); } else { intmask = readl(host->ioaddr + SDHCI_SIGNAL_ENABLE); intmask &= ~(SDHCI_INT_BUF_EMPTY | SDHCI_INT_BUF_FULL); writel(intmask, host->ioaddr + SDHCI_SIGNAL_ENABLE); intmask = readl(host->ioaddr + SDHCI_INT_ENABLE); intmask &= ~(SDHCI_INT_BUF_EMPTY | SDHCI_INT_BUF_FULL); writel(intmask, host->ioaddr + SDHCI_INT_ENABLE); } /* * Controller doesn't count down when in single block mode. */ if ((data->blocks == 1) && (data->error == MMC_ERR_NONE)) blocks = 0; else blocks = readw(host->ioaddr + SDHCI_BLOCK_COUNT); data->bytes_xfered = data->blksz * (data->blocks - blocks); if ((data->error == MMC_ERR_NONE) && blocks) { printk(KERN_ERR "%s: Controller signalled completion even " "though there were blocks left. Please report this " "to " BUGMAIL ".\n", mmc_hostname(host->mmc)); data->error = MMC_ERR_FAILED; } if (host->size != 0) { printk(KERN_ERR "%s: %d bytes were left untransferred. " "Please report this to " BUGMAIL ".\n", mmc_hostname(host->mmc), host->size); data->error = MMC_ERR_FAILED; } DBG("Ending data transfer (%d bytes)\n", data->bytes_xfered); if (data->stop) { /* * The controller needs a reset of internal state machines * upon error conditions. */ if (data->error != MMC_ERR_NONE) { sdhci_reset(host, SDHCI_RESET_CMD); sdhci_reset(host, SDHCI_RESET_DATA); } sdhci_send_command(host, data->stop); } else tasklet_schedule(&host->finish_tasklet); } static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) { int flags; u32 present; unsigned long max_jiffies; WARN_ON(host->cmd); DBG("Sending cmd (%x)\n", cmd->opcode); /* Wait max 10 ms */ max_jiffies = jiffies + (HZ + 99)/100; do { if (time_after(jiffies, max_jiffies)) { printk(KERN_ERR "%s: Controller never released " "inhibit bits. Please report this to " BUGMAIL ".\n", mmc_hostname(host->mmc)); sdhci_dumpregs(host); cmd->error = MMC_ERR_FAILED; tasklet_schedule(&host->finish_tasklet); return; } present = readl(host->ioaddr + SDHCI_PRESENT_STATE); } while (present & (SDHCI_CMD_INHIBIT | SDHCI_DATA_INHIBIT)); mod_timer(&host->timer, jiffies + 10 * HZ); host->cmd = cmd; sdhci_prepare_data(host, cmd->data); writel(cmd->arg, host->ioaddr + SDHCI_ARGUMENT); if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) { printk(KERN_ERR "%s: Unsupported response type! " "Please report this to " BUGMAIL ".\n", mmc_hostname(host->mmc)); cmd->error = MMC_ERR_INVALID; tasklet_schedule(&host->finish_tasklet); return; } if (!(cmd->flags & MMC_RSP_PRESENT)) flags = SDHCI_CMD_RESP_NONE; else if (cmd->flags & MMC_RSP_136) flags = SDHCI_CMD_RESP_LONG; else if (cmd->flags & MMC_RSP_BUSY) flags = SDHCI_CMD_RESP_SHORT_BUSY; else flags = SDHCI_CMD_RESP_SHORT; if (cmd->flags & MMC_RSP_CRC) flags |= SDHCI_CMD_CRC; if (cmd->flags & MMC_RSP_OPCODE) flags |= SDHCI_CMD_INDEX; if (cmd->data) flags |= SDHCI_CMD_DATA; writel(SDHCI_MAKE_CMD(cmd->opcode, flags), host->ioaddr + SDHCI_COMMAND); } static void sdhci_finish_command(struct sdhci_host *host) { int i; BUG_ON(host->cmd == NULL); if (host->cmd->flags & MMC_RSP_PRESENT) { if (host->cmd->flags & MMC_RSP_136) { /* CRC is stripped so we need to do some shifting. */ for (i = 0;i < 4;i++) { host->cmd->resp[i] = readl(host->ioaddr + SDHCI_RESPONSE + (3-i)*4) << 8; if (i != 3) host->cmd->resp[i] |= readb(host->ioaddr + SDHCI_RESPONSE + (3-i)*4-1); } } else { host->cmd->resp[0] = readl(host->ioaddr + SDHCI_RESPONSE); } } host->cmd->error = MMC_ERR_NONE; DBG("Ending cmd (%x)\n", host->cmd->opcode); if (host->cmd->data) { u32 intmask; host->data = host->cmd->data; if (!(host->flags & SDHCI_USE_DMA)) { /* * Don't enable the interrupts until now to make sure we * get stable handling of the FIFO. */ intmask = readl(host->ioaddr + SDHCI_INT_ENABLE); intmask |= SDHCI_INT_BUF_EMPTY | SDHCI_INT_BUF_FULL; writel(intmask, host->ioaddr + SDHCI_INT_ENABLE); intmask = readl(host->ioaddr + SDHCI_SIGNAL_ENABLE); intmask |= SDHCI_INT_BUF_EMPTY | SDHCI_INT_BUF_FULL; writel(intmask, host->ioaddr + SDHCI_SIGNAL_ENABLE); /* * The buffer interrupts are to unreliable so we * start the transfer immediatly. */ sdhci_transfer_pio(host); } } else tasklet_schedule(&host->finish_tasklet); host->cmd = NULL; } static void sdhci_set_clock(struct sdhci_host *host, unsigned int clock) { int div; u16 clk; unsigned long max_jiffies; if (clock == host->clock) return; writew(0, host->ioaddr + SDHCI_CLOCK_CONTROL); if (clock == 0) goto out; for (div = 1;div < 256;div *= 2) { if ((host->max_clk / div) <= clock) break; } div >>= 1; clk = div << SDHCI_DIVIDER_SHIFT; clk |= SDHCI_CLOCK_INT_EN; writew(clk, host->ioaddr + SDHCI_CLOCK_CONTROL); /* Wait max 10 ms */ max_jiffies = jiffies + (HZ + 99)/100; do { if (time_after(jiffies, max_jiffies)) { printk(KERN_ERR "%s: Internal clock never stabilised. " "Please report this to " BUGMAIL ".\n", mmc_hostname(host->mmc)); sdhci_dumpregs(host); return; } clk = readw(host->ioaddr + SDHCI_CLOCK_CONTROL); } while (!(clk & SDHCI_CLOCK_INT_STABLE)); clk |= SDHCI_CLOCK_CARD_EN; writew(clk, host->ioaddr + SDHCI_CLOCK_CONTROL); out: host->clock = clock; } /*****************************************************************************\ * * * MMC callbacks * * * \*****************************************************************************/ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) { struct sdhci_host *host; unsigned long flags; host = mmc_priv(mmc); spin_lock_irqsave(&host->lock, flags); WARN_ON(host->mrq != NULL); sdhci_activate_led(host); host->mrq = mrq; if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) { host->mrq->cmd->error = MMC_ERR_TIMEOUT; tasklet_schedule(&host->finish_tasklet); } else sdhci_send_command(host, mrq->cmd); spin_unlock_irqrestore(&host->lock, flags); } static void sdhci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) { struct sdhci_host *host; unsigned long flags; u8 ctrl; host = mmc_priv(mmc); spin_lock_irqsave(&host->lock, flags); /* * Reset the chip on each power off. * Should clear out any weird states. */ if (ios->power_mode == MMC_POWER_OFF) { writel(0, host->ioaddr + SDHCI_SIGNAL_ENABLE); spin_unlock_irqrestore(&host->lock, flags); sdhci_init(host); spin_lock_irqsave(&host->lock, flags); } sdhci_set_clock(host, ios->clock); if (ios->power_mode == MMC_POWER_OFF) writeb(0, host->ioaddr + SDHCI_POWER_CONTROL); else writeb(0xFF, host->ioaddr + SDHCI_POWER_CONTROL); ctrl = readb(host->ioaddr + SDHCI_HOST_CONTROL); if (ios->bus_width == MMC_BUS_WIDTH_4) ctrl |= SDHCI_CTRL_4BITBUS; else ctrl &= ~SDHCI_CTRL_4BITBUS; writeb(ctrl, host->ioaddr + SDHCI_HOST_CONTROL); spin_unlock_irqrestore(&host->lock, flags); } static int sdhci_get_ro(struct mmc_host *mmc) { struct sdhci_host *host; unsigned long flags; int present; host = mmc_priv(mmc); spin_lock_irqsave(&host->lock, flags); present = readl(host->ioaddr + SDHCI_PRESENT_STATE); spin_unlock_irqrestore(&host->lock, flags); return !(present & SDHCI_WRITE_PROTECT); } static struct mmc_host_ops sdhci_ops = { .request = sdhci_request, .set_ios = sdhci_set_ios, .get_ro = sdhci_get_ro, }; /*****************************************************************************\ * * * Tasklets * * * \*****************************************************************************/ static void sdhci_tasklet_card(unsigned long param) { struct sdhci_host *host; unsigned long flags; host = (struct sdhci_host*)param; spin_lock_irqsave(&host->lock, flags); if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) { if (host->mrq) { printk(KERN_ERR "%s: Card removed during transfer!\n", mmc_hostname(host->mmc)); printk(KERN_ERR "%s: Resetting controller.\n", mmc_hostname(host->mmc)); sdhci_reset(host, SDHCI_RESET_CMD); sdhci_reset(host, SDHCI_RESET_DATA); host->mrq->cmd->error = MMC_ERR_FAILED; tasklet_schedule(&host->finish_tasklet); } } spin_unlock_irqrestore(&host->lock, flags); mmc_detect_change(host->mmc, msecs_to_jiffies(500)); } static void sdhci_tasklet_finish(unsigned long param) { struct sdhci_host *host; unsigned long flags; struct mmc_request *mrq; host = (struct sdhci_host*)param; spin_lock_irqsave(&host->lock, flags); del_timer(&host->timer); mrq = host->mrq; DBG("Ending request, cmd (%x)\n", mrq->cmd->opcode); /* * The controller needs a reset of internal state machines * upon error conditions. */ if ((mrq->cmd->error != MMC_ERR_NONE) || (mrq->data && ((mrq->data->error != MMC_ERR_NONE) || (mrq->data->stop && (mrq->data->stop->error != MMC_ERR_NONE))))) { sdhci_reset(host, SDHCI_RESET_CMD); sdhci_reset(host, SDHCI_RESET_DATA); } host->mrq = NULL; host->cmd = NULL; host->data = NULL; sdhci_deactivate_led(host); spin_unlock_irqrestore(&host->lock, flags); mmc_request_done(host->mmc, mrq); } static void sdhci_timeout_timer(unsigned long data) { struct sdhci_host *host; unsigned long flags; host = (struct sdhci_host*)data; spin_lock_irqsave(&host->lock, flags); if (host->mrq) { printk(KERN_ERR "%s: Timeout waiting for hardware interrupt. " "Please report this to " BUGMAIL ".\n", mmc_hostname(host->mmc)); sdhci_dumpregs(host); if (host->data) { host->data->error = MMC_ERR_TIMEOUT; sdhci_finish_data(host); } else { if (host->cmd) host->cmd->error = MMC_ERR_TIMEOUT; else host->mrq->cmd->error = MMC_ERR_TIMEOUT; tasklet_schedule(&host->finish_tasklet); } } spin_unlock_irqrestore(&host->lock, flags); } /*****************************************************************************\ * * * Interrupt handling * * * \*****************************************************************************/ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask) { BUG_ON(intmask == 0); if (!host->cmd) { printk(KERN_ERR "%s: Got command interrupt even though no " "command operation was in progress.\n", mmc_hostname(host->mmc)); printk(KERN_ERR "%s: Please report this to " BUGMAIL ".\n", mmc_hostname(host->mmc)); sdhci_dumpregs(host); return; } if (intmask & SDHCI_INT_RESPONSE) sdhci_finish_command(host); else { if (intmask & SDHCI_INT_TIMEOUT) host->cmd->error = MMC_ERR_TIMEOUT; else if (intmask & SDHCI_INT_CRC) host->cmd->error = MMC_ERR_BADCRC; else if (intmask & (SDHCI_INT_END_BIT | SDHCI_INT_INDEX)) host->cmd->error = MMC_ERR_FAILED; else host->cmd->error = MMC_ERR_INVALID; tasklet_schedule(&host->finish_tasklet); } } static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) { BUG_ON(intmask == 0); if (!host->data) { /* * A data end interrupt is sent together with the response * for the stop command. */ if (intmask & SDHCI_INT_DATA_END) return; printk(KERN_ERR "%s: Got data interrupt even though no " "data operation was in progress.\n", mmc_hostname(host->mmc)); printk(KERN_ERR "%s: Please report this to " BUGMAIL ".\n", mmc_hostname(host->mmc)); sdhci_dumpregs(host); return; } if (intmask & SDHCI_INT_DATA_TIMEOUT) host->data->error = MMC_ERR_TIMEOUT; else if (intmask & SDHCI_INT_DATA_CRC) host->data->error = MMC_ERR_BADCRC; else if (intmask & SDHCI_INT_DATA_END_BIT) host->data->error = MMC_ERR_FAILED; if (host->data->error != MMC_ERR_NONE) sdhci_finish_data(host); else { if (intmask & (SDHCI_INT_BUF_FULL | SDHCI_INT_BUF_EMPTY)) sdhci_transfer_pio(host); if (intmask & SDHCI_INT_DATA_END) sdhci_finish_data(host); } } static irqreturn_t sdhci_irq(int irq, void *dev_id, struct pt_regs *regs) { irqreturn_t result; struct sdhci_host* host = dev_id; u32 intmask; spin_lock(&host->lock); intmask = readl(host->ioaddr + SDHCI_INT_STATUS); if (!intmask) { result = IRQ_NONE; goto out; } DBG("*** %s got interrupt: 0x%08x\n", host->slot_descr, intmask); if (intmask & (SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE)) tasklet_schedule(&host->card_tasklet); if (intmask & SDHCI_INT_CMD_MASK) { sdhci_cmd_irq(host, intmask & SDHCI_INT_CMD_MASK); writel(intmask & SDHCI_INT_CMD_MASK, host->ioaddr + SDHCI_INT_STATUS); } if (intmask & SDHCI_INT_DATA_MASK) { sdhci_data_irq(host, intmask & SDHCI_INT_DATA_MASK); writel(intmask & SDHCI_INT_DATA_MASK, host->ioaddr + SDHCI_INT_STATUS); } intmask &= ~(SDHCI_INT_CMD_MASK | SDHCI_INT_DATA_MASK); if (intmask & SDHCI_INT_CARD_INT) { printk(KERN_ERR "%s: Unexpected card interrupt. Please " "report this to " BUGMAIL ".\n", mmc_hostname(host->mmc)); sdhci_dumpregs(host); } if (intmask & SDHCI_INT_BUS_POWER) { printk(KERN_ERR "%s: Unexpected bus power interrupt. Please " "report this to " BUGMAIL ".\n", mmc_hostname(host->mmc)); sdhci_dumpregs(host); } if (intmask & SDHCI_INT_ACMD12ERR) { printk(KERN_ERR "%s: Unexpected auto CMD12 error. Please " "report this to " BUGMAIL ".\n", mmc_hostname(host->mmc)); sdhci_dumpregs(host); writew(~0, host->ioaddr + SDHCI_ACMD12_ERR); } if (intmask) writel(intmask, host->ioaddr + SDHCI_INT_STATUS); result = IRQ_HANDLED; out: spin_unlock(&host->lock); return result; } /*****************************************************************************\ * * * Suspend/resume * * * \*****************************************************************************/ #ifdef CONFIG_PM static int sdhci_suspend (struct pci_dev *pdev, pm_message_t state) { struct sdhci_chip *chip; int i, ret; chip = pci_get_drvdata(pdev); if (!chip) return 0; DBG("Suspending...\n"); for (i = 0;i < chip->num_slots;i++) { if (!chip->hosts[i]) continue; ret = mmc_suspend_host(chip->hosts[i]->mmc, state); if (ret) { for (i--;i >= 0;i--) mmc_resume_host(chip->hosts[i]->mmc); return ret; } } 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 sdhci_resume (struct pci_dev *pdev) { struct sdhci_chip *chip; int i, ret; chip = pci_get_drvdata(pdev); if (!chip) return 0; DBG("Resuming...\n"); pci_set_power_state(pdev, PCI_D0); pci_restore_state(pdev); pci_enable_device(pdev); for (i = 0;i < chip->num_slots;i++) { if (!chip->hosts[i]) continue; if (chip->hosts[i]->flags & SDHCI_USE_DMA) pci_set_master(pdev); sdhci_init(chip->hosts[i]); ret = mmc_resume_host(chip->hosts[i]->mmc); if (ret) return ret; } return 0; } #else /* CONFIG_PM */ #define sdhci_suspend NULL #define sdhci_resume NULL #endif /* CONFIG_PM */ /*****************************************************************************\ * * * Device probing/removal * * * \*****************************************************************************/ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot) { int ret; struct sdhci_chip *chip; struct mmc_host *mmc; struct sdhci_host *host; u8 first_bar; unsigned int caps; chip = pci_get_drvdata(pdev); BUG_ON(!chip); ret = pci_read_config_byte(pdev, PCI_SLOT_INFO, &first_bar); if (ret) return ret; first_bar &= PCI_SLOT_INFO_FIRST_BAR_MASK; if (first_bar > 5) { printk(KERN_ERR DRIVER_NAME ": Invalid first BAR. Aborting.\n"); return -ENODEV; } if (!(pci_resource_flags(pdev, first_bar + slot) & IORESOURCE_MEM)) { printk(KERN_ERR DRIVER_NAME ": BAR is not iomem. Aborting.\n"); return -ENODEV; } if (pci_resource_len(pdev, first_bar + slot) != 0x100) { printk(KERN_ERR DRIVER_NAME ": Invalid iomem size. Aborting.\n"); return -ENODEV; } mmc = mmc_alloc_host(sizeof(struct sdhci_host), &pdev->dev); if (!mmc) return -ENOMEM; host = mmc_priv(mmc); host->mmc = mmc; host->bar = first_bar + slot; host->addr = pci_resource_start(pdev, host->bar); host->irq = pdev->irq; DBG("slot %d at 0x%08lx, irq %d\n", slot, host->addr, host->irq); snprintf(host->slot_descr, 20, "sdhci:slot%d", slot); ret = pci_request_region(pdev, host->bar, host->slot_descr); if (ret) goto free; host->ioaddr = ioremap_nocache(host->addr, pci_resource_len(pdev, host->bar)); if (!host->ioaddr) { ret = -ENOMEM; goto release; } caps = readl(host->ioaddr + SDHCI_CAPABILITIES); if ((caps & SDHCI_CAN_DO_DMA) && ((pdev->class & 0x0000FF) == 0x01)) host->flags |= SDHCI_USE_DMA; if (host->flags & SDHCI_USE_DMA) { if (pci_set_dma_mask(pdev, DMA_32BIT_MASK)) { printk(KERN_WARNING "%s: No suitable DMA available. " "Falling back to PIO.\n", host->slot_descr); host->flags &= ~SDHCI_USE_DMA; } } if (host->flags & SDHCI_USE_DMA) pci_set_master(pdev); else /* XXX: Hack to get MMC layer to avoid highmem */ pdev->dma_mask = 0; host->max_clk = (caps & SDHCI_CLOCK_BASE_MASK) >> SDHCI_CLOCK_BASE_SHIFT; host->max_clk *= 1000000; /* * Set host parameters. */ mmc->ops = &sdhci_ops; mmc->f_min = host->max_clk / 256; mmc->f_max = host->max_clk; mmc->ocr_avail = MMC_VDD_32_33|MMC_VDD_33_34; mmc->caps = MMC_CAP_4_BIT_DATA; spin_lock_init(&host->lock); /* * Maximum number of segments. Hardware cannot do scatter lists. */ if (host->flags & SDHCI_USE_DMA) mmc->max_hw_segs = 1; else mmc->max_hw_segs = 16; mmc->max_phys_segs = 16; /* * Maximum number of sectors in one transfer. Limited by sector * count register. */ mmc->max_sectors = 0x3FFF; /* * Maximum segment size. Could be one segment with the maximum number * of sectors. */ mmc->max_seg_size = mmc->max_sectors * 512; /* * Init tasklets. */ tasklet_init(&host->card_tasklet, sdhci_tasklet_card, (unsigned long)host); tasklet_init(&host->finish_tasklet, sdhci_tasklet_finish, (unsigned long)host); setup_timer(&host->timer, sdhci_timeout_timer, (long)host); ret = request_irq(host->irq, sdhci_irq, SA_SHIRQ, host->slot_descr, host); if (ret) goto unmap; sdhci_init(host); #ifdef CONFIG_MMC_DEBUG sdhci_dumpregs(host); #endif host->chip = chip; chip->hosts[slot] = host; mmc_add_host(mmc); printk(KERN_INFO "%s: SDHCI at 0x%08lx irq %d %s\n", mmc_hostname(mmc), host->addr, host->irq, (host->flags & SDHCI_USE_DMA)?"DMA":"PIO"); return 0; unmap: tasklet_kill(&host->card_tasklet); tasklet_kill(&host->finish_tasklet); iounmap(host->ioaddr); release: pci_release_region(pdev, host->bar); free: mmc_free_host(mmc); return ret; } static void sdhci_remove_slot(struct pci_dev *pdev, int slot) { struct sdhci_chip *chip; struct mmc_host *mmc; struct sdhci_host *host; chip = pci_get_drvdata(pdev); host = chip->hosts[slot]; mmc = host->mmc; chip->hosts[slot] = NULL; mmc_remove_host(mmc); sdhci_reset(host, SDHCI_RESET_ALL); free_irq(host->irq, host); del_timer_sync(&host->timer); tasklet_kill(&host->card_tasklet); tasklet_kill(&host->finish_tasklet); iounmap(host->ioaddr); pci_release_region(pdev, host->bar); mmc_free_host(mmc); } static int __devinit sdhci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { int ret, i; u8 slots; struct sdhci_chip *chip; BUG_ON(pdev == NULL); BUG_ON(ent == NULL); DBG("found at %s\n", pci_name(pdev)); ret = pci_read_config_byte(pdev, PCI_SLOT_INFO, &slots); if (ret) return ret; slots = PCI_SLOT_INFO_SLOTS(slots) + 1; DBG("found %d slot(s)\n", slots); if (slots == 0) return -ENODEV; ret = pci_enable_device(pdev); if (ret) return ret; chip = kzalloc(sizeof(struct sdhci_chip) + sizeof(struct sdhci_host*) * slots, GFP_KERNEL); if (!chip) { ret = -ENOMEM; goto err; } chip->pdev = pdev; chip->num_slots = slots; pci_set_drvdata(pdev, chip); for (i = 0;i < slots;i++) { ret = sdhci_probe_slot(pdev, i); if (ret) { for (i--;i >= 0;i--) sdhci_remove_slot(pdev, i); goto free; } } return 0; free: pci_set_drvdata(pdev, NULL); kfree(chip); err: pci_disable_device(pdev); return ret; } static void __devexit sdhci_remove(struct pci_dev *pdev) { int i; struct sdhci_chip *chip; chip = pci_get_drvdata(pdev); if (chip) { for (i = 0;i < chip->num_slots;i++) sdhci_remove_slot(pdev, i); pci_set_drvdata(pdev, NULL); kfree(chip); } pci_disable_device(pdev); } static struct pci_driver sdhci_driver = { .name = DRIVER_NAME, .id_table = pci_ids, .probe = sdhci_probe, .remove = __devexit_p(sdhci_remove), .suspend = sdhci_suspend, .resume = sdhci_resume, }; /*****************************************************************************\ * * * Driver init/exit * * * \*****************************************************************************/ static int __init sdhci_drv_init(void) { printk(KERN_INFO DRIVER_NAME ": Secure Digital Host Controller Interface driver, " DRIVER_VERSION "\n"); printk(KERN_INFO DRIVER_NAME ": Copyright(c) Pierre Ossman\n"); return pci_register_driver(&sdhci_driver); } static void __exit sdhci_drv_exit(void) { DBG("Exiting\n"); pci_unregister_driver(&sdhci_driver); } module_init(sdhci_drv_init); module_exit(sdhci_drv_exit); MODULE_AUTHOR("Pierre Ossman <drzeus@drzeus.cx>"); MODULE_DESCRIPTION("Secure Digital Host Controller Interface driver"); MODULE_VERSION(DRIVER_VERSION); MODULE_LICENSE("GPL");