--- zzzz-none-000/linux-4.9.279/drivers/mmc/host/sdhci.c 2021-08-08 06:38:54.000000000 +0000 +++ puma7-atom-6591-750/linux-4.9.279/drivers/mmc/host/sdhci.c 2023-02-08 11:43:42.000000000 +0000 @@ -30,7 +30,10 @@ #include #include #include - +#include +#ifdef CONFIG_HW_MUTEXES +#include +#endif #include "sdhci.h" #define DRIVER_NAME "sdhci" @@ -47,11 +50,99 @@ static void sdhci_enable_preset_value(struct sdhci_host *host, bool enable); +static const char __maybe_unused *sdhci_error_str[] = { + /* Error Interrupt Status Register bits */ + [15] = "vendor3", + [14] = "vendor2", + [13] = "vendor1", + [12] = "vendor0", + [11] = "response_error", + [10] = "tuning_error", + [9] = "adma_error", + [8] = "auto_cmd_error", + [7] = "current_limit_error", + [6] = "data_end_bit_error", + [5] = "data_crc_error", + [4] = "data_timeout_error", + [3] = "command_index_error", + [2] = "command_endbit_error", + [1] = "command_crc_error", + [0] = "command_timeout_error", +}; + +static int sdhci_errcnt_show(void *data, u64 *val) +{ + atomic_t *errcnt = data; + + *val = atomic_read(errcnt); + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(sdhci_errcnt_fops, sdhci_errcnt_show, NULL, "%llu\n"); + +#ifdef CONFIG_AVM_MMC_SDHCI_ERRORSTATS +static void sdhci_zero_error_statistics(struct sdhci_host *host) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(host->errcnt); ++i) + atomic_set(&host->errcnt[i], 0); + +} + +static void sdhci_add_debugfs(struct sdhci_host *host) +{ + struct mmc_host *mmc = host->mmc; + struct dentry *sdhci_root, *attr; + size_t i; + + if (!mmc->debugfs_root) + return; + + BUILD_BUG_ON(ARRAY_SIZE(host->errcnt) != ARRAY_SIZE(sdhci_error_str)); + + sdhci_root = debugfs_create_dir("sdhci_errors", mmc->debugfs_root); + if (IS_ERR(sdhci_root)) /* No debugfs */ + return; + if (!sdhci_root) + goto err_root; + + for (i = 0; i < ARRAY_SIZE(sdhci_error_str); ++i) { + BUG_ON(!sdhci_error_str[i]); + attr = debugfs_create_file(sdhci_error_str[i], + 0400, + sdhci_root, + &host->errcnt[i], + &sdhci_errcnt_fops); + if (!attr) + goto err_attr; + } + + return; + +err_attr: + debugfs_remove_recursive(sdhci_root); +err_root: + pr_err("%s: Error creating sdhci debugfs\n", mmc_hostname(mmc)); +} +#else +static void sdhci_zero_error_statistics(struct sdhci_host *host) { } + +static void sdhci_add_debugfs(struct sdhci_host *host) { } +#endif + +#ifdef CONFIG_TFFS_PANIC_LOG +static int avm_sdhci_sync_io_init(struct mmc_host *mmc); +static int avm_sdhci_sync_io_blocks(struct mmc_host *mmc, sector_t blk, + size_t len, u8 *in, const u8 *out); +#endif + static void sdhci_dumpregs(struct sdhci_host *host) { pr_err(DRIVER_NAME ": =========== REGISTER DUMP (%s)===========\n", mmc_hostname(host->mmc)); + pr_err(DRIVER_NAME ": Host flags: 0x%08x\n", host->flags); pr_err(DRIVER_NAME ": Sys addr: 0x%08x | Version: 0x%08x\n", sdhci_readl(host, SDHCI_DMA_ADDRESS), sdhci_readw(host, SDHCI_HOST_VERSION)); @@ -100,6 +191,16 @@ readl(host->ioaddr + SDHCI_ADMA_ADDRESS)); } + if (SDHCI_HOST_SUPPORTS_HW_MUTEX(host)) + pr_err(DRIVER_NAME ": H/W mutex: %s\n", + EMMC_HW_MUTEX_IS_LOCKED() ? "Locked" : "Free"); + /* + * When checking for a claimer, we shouldn't deref the claimer + * pointer, since we don't hold a reference to the task. + */ + pr_err(DRIVER_NAME ": Host is %sclaimed by a task\n", + host->mmc->claimer ? "" : "not "); + pr_err(DRIVER_NAME ": ===========================================\n"); } @@ -133,7 +234,8 @@ } sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); - sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); + if (!(host->flags & SDHCI_PANIC_SYNC)) + sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); } static void sdhci_enable_card_detection(struct sdhci_host *host) @@ -217,23 +319,26 @@ static void sdhci_init(struct sdhci_host *host, int soft) { struct mmc_host *mmc = host->mmc; - if (soft) sdhci_do_reset(host, SDHCI_RESET_CMD|SDHCI_RESET_DATA); else sdhci_do_reset(host, SDHCI_RESET_ALL); host->ier = SDHCI_INT_BUS_POWER | SDHCI_INT_DATA_END_BIT | - SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_TIMEOUT | - SDHCI_INT_INDEX | SDHCI_INT_END_BIT | SDHCI_INT_CRC | - SDHCI_INT_TIMEOUT | SDHCI_INT_DATA_END | - SDHCI_INT_RESPONSE; + SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_TIMEOUT | + SDHCI_INT_INDEX | SDHCI_INT_END_BIT | SDHCI_INT_CRC | + SDHCI_INT_TIMEOUT | SDHCI_INT_DATA_END | + SDHCI_INT_RESPONSE; if (host->tuning_mode == SDHCI_TUNING_MODE_2 || host->tuning_mode == SDHCI_TUNING_MODE_3) host->ier |= SDHCI_INT_RETUNE; sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); + + if (host->flags & SDHCI_PANIC_SYNC) + return; + sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); if (soft) { @@ -243,9 +348,10 @@ } } -static void sdhci_reinit(struct sdhci_host *host) +static void sdhci_reinit(struct sdhci_host *host, int soft) { - sdhci_init(host, 0); + sdhci_init(host, soft); + sdhci_enable_card_detection(host); } @@ -733,6 +839,8 @@ host->ier = (host->ier & ~dma_irqs) | pio_irqs; sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); + if (host->flags & SDHCI_PANIC_SYNC) + return; sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); } @@ -1252,6 +1360,25 @@ return preset; } +void sdhci_clock_gating(struct sdhci_host *host, bool enable) +{ + u16 clk; + + clk = sdhci_readw(host, SDHCI_CLOCK_CONTROL); + /* Check if bit 2 already has requested value */ + if ((clk & SDHCI_CLOCK_CARD_EN) != enable) + { + if (enable) { + clk |= SDHCI_CLOCK_CARD_EN; + } + else { + clk &= ~SDHCI_CLOCK_CARD_EN; + } + dev_dbg(mmc_dev(host->mmc), "Clock gating clk=0x%x, enable=%d\n", clk, enable); + sdhci_writew(host, clk, SDHCI_CLOCK_CONTROL); + } +} + u16 sdhci_calc_clk(struct sdhci_host *host, unsigned int clock, unsigned int *actual_clock) { @@ -1260,6 +1387,7 @@ u16 clk = 0; bool switch_base_clk = false; + if (host->version >= SDHCI_SPEC_300) { if (host->preset_enabled) { u16 pre_val; @@ -1388,9 +1516,11 @@ { struct mmc_host *mmc = host->mmc; - spin_unlock_irq(&host->lock); - mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, vdd); - spin_lock_irq(&host->lock); + if (!(host->flags & SDHCI_PANIC_SYNC)) { + spin_unlock_irq(&host->lock); + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, vdd); + spin_lock_irq(&host->lock); + } if (mode != MMC_POWER_OFF) sdhci_writeb(host, SDHCI_POWER_ON, SDHCI_POWER_CONTROL); @@ -1585,7 +1715,8 @@ if (host->flags & SDHCI_DEVICE_DEAD) { spin_unlock_irqrestore(&host->lock, flags); if (!IS_ERR(mmc->supply.vmmc) && - ios->power_mode == MMC_POWER_OFF) + ios->power_mode == MMC_POWER_OFF && + !(host->flags & SDHCI_PANIC_SYNC)) mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0); return; } @@ -1596,7 +1727,7 @@ */ if (ios->power_mode == MMC_POWER_OFF) { sdhci_writel(host, 0, SDHCI_SIGNAL_ENABLE); - sdhci_reinit(host); + sdhci_reinit(host, 0); } if (host->version >= SDHCI_SPEC_300 && @@ -1735,10 +1866,35 @@ spin_unlock_irqrestore(&host->lock, flags); } +int sdhci_is_card_stable(struct sdhci_host *host) +{ + return !!(sdhci_readl(host, SDHCI_PRESENT_STATE) & SDHCI_CARD_IS_STABLE); +} + +void sdhci_wait_for_card_stable(struct sdhci_host *host) +{ + int cnt = 0; + + while(!sdhci_is_card_stable(host) && cnt < 12) + { + mdelay(100); + cnt++; + } + + if(!sdhci_is_card_stable(host)) + { + pr_err("%s: Card Present signal not stable\n", mmc_hostname(host->mmc)); + } +} + static int sdhci_get_cd(struct mmc_host *mmc) { struct sdhci_host *host = mmc_priv(mmc); - int gpio_cd = mmc_gpio_get_cd(mmc); + int gpio_cd; + + sdhci_wait_for_card_stable(host); + + gpio_cd = mmc_gpio_get_cd(mmc); if (host->flags & SDHCI_DEVICE_DEAD) return 0; @@ -1822,7 +1978,8 @@ host->ier &= ~SDHCI_INT_CARD_INT; sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); - sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); + if (!(host->flags & SDHCI_PANIC_SYNC)) + sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); mmiowb(); } } @@ -2305,6 +2462,10 @@ .select_drive_strength = sdhci_select_drive_strength, .card_event = sdhci_card_event, .card_busy = sdhci_card_busy, +#ifdef CONFIG_TFFS_PANIC_LOG + .avm_sync_io_init = avm_sdhci_sync_io_init, + .avm_sync_io_rw_blocks = avm_sdhci_sync_io_blocks, +#endif }; /*****************************************************************************\ @@ -2396,7 +2557,7 @@ { struct sdhci_host *host = (struct sdhci_host *)param; - while (!sdhci_request_done(host)) + while (!(host->flags & SDHCI_PANIC_SYNC) && !sdhci_request_done(host)) ; } @@ -2409,6 +2570,11 @@ spin_lock_irqsave(&host->lock, flags); + if (host->flags & SDHCI_PANIC_SYNC) { + spin_unlock_irqrestore(&host->lock, flags); + return; + } + if (host->cmd && !sdhci_data_line_cmd(host->cmd)) { pr_err("%s: Timeout waiting for hardware cmd interrupt.\n", mmc_hostname(host->mmc)); @@ -2656,6 +2822,32 @@ } } +#ifdef CONFIG_AVM_MMC_SDHCI_ERRORSTATS +static void sdhci_update_error_statistics(struct sdhci_host *host, u32 intmask) +{ + struct mmc_host *mmc = host->mmc; + unsigned long err; + unsigned int n; + + /* Ignore global error bit SDHCI_INT_ERROR and all non-error, lower + * bits; SDHCI_INT_ERROR bit signals only that more interesting error + * bits are set in the high word. + */ + err = intmask >> 16; + if (!err) + return; + + pr_debug_ratelimited("%s: sdhci error, int status = 0x%08x\n", + mmc_hostname(mmc), intmask); + BUILD_BUG_ON(ARRAY_SIZE(host->errcnt) != 16); + for_each_set_bit(n, &err, 16) + atomic_inc(&host->errcnt[n]); +} +#else +static void sdhci_update_error_statistics(struct sdhci_host *host, u32 intmask) +{ } +#endif + static irqreturn_t sdhci_irq(int irq, void *dev_id) { irqreturn_t result = IRQ_NONE; @@ -2663,6 +2855,22 @@ u32 intmask, mask, unexpected = 0; int max_loops = 16; +#ifdef CONFIG_HW_MUTEXES + /* eMMC card interrupt can be classified as: + * 1: Insert/Remove interrupt + * 2: Data/Command interrupt + * 3: Unexpected error interrupt + * It is assumed that interrupts happen only when a task owns the + * HW Mutex + */ + + if (MMC_HOST_SUPPORTS_HW_MUTEX(host->mmc)) { + if (!EMMC_HW_MUTEX_IS_LOCKED()) { + return IRQ_NONE; + } + } +#endif + spin_lock(&host->lock); if (host->runtime_suspended && !sdhci_sdio_irq_enabled(host)) { @@ -2670,6 +2878,11 @@ return IRQ_NONE; } + if (host->flags & SDHCI_PANIC_SYNC) { + result = IRQ_HANDLED; + goto out; + } + intmask = sdhci_readl(host, SDHCI_INT_STATUS); if (!intmask || intmask == 0xffffffff) { result = IRQ_NONE; @@ -2677,6 +2890,8 @@ } do { + sdhci_update_error_statistics(host, intmask); + /* Clear selected interrupts. */ mask = intmask & (SDHCI_INT_CMD_MASK | SDHCI_INT_DATA_MASK | SDHCI_INT_BUS_POWER); @@ -2908,13 +3123,27 @@ if (host->tuning_mode != SDHCI_TUNING_MODE_3) mmc_retune_needed(host->mmc); - spin_lock_irqsave(&host->lock, flags); - host->ier &= SDHCI_INT_CARD_INT; - sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); - sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); - spin_unlock_irqrestore(&host->lock, flags); + if (MMC_HOST_SUPPORTS_HW_MUTEX(host->mmc)) + { + /* + * This host controller is shared with another processor. Instead of + * masking interrupts at the host controller level, do it at the OS + * level. Danger: this irq must not be shared, or it will obviously + * affect other devices sharing the irq. + */ + disable_irq(host->irq); + } + else + { + spin_lock_irqsave(&host->lock, flags); + host->ier &= SDHCI_INT_CARD_INT; + sdhci_writel(host, host->ier, SDHCI_INT_ENABLE); + if (!(host->flags & SDHCI_PANIC_SYNC)) + sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE); + spin_unlock_irqrestore(&host->lock, flags); - synchronize_hardirq(host->irq); + synchronize_hardirq(host->irq); + } spin_lock_irqsave(&host->lock, flags); host->runtime_suspended = true; @@ -2930,6 +3159,15 @@ unsigned long flags; int host_flags = host->flags; + if (MMC_HOST_SUPPORTS_HW_MUTEX(host->mmc)) + { + /* + * Unmask the interrupt at the OS level. Corresponds to disable_irq() + * call in sdhci_runtime_suspend_host(). + */ + enable_irq(host->irq); + } + if (host_flags & (SDHCI_USE_SDMA | SDHCI_USE_ADMA)) { if (host->ops->enable_dma) host->ops->enable_dma(host); @@ -2998,6 +3236,12 @@ host->flags = SDHCI_SIGNALING_330; +#ifdef CONFIG_HW_MUTEXES + host->irq_enable_count = 0; +#endif + + sdhci_zero_error_statistics(host); + return host; } @@ -3615,6 +3859,9 @@ if (ret) goto unled; + sdhci_zero_error_statistics(host); + sdhci_add_debugfs(host); + pr_info("%s: SDHCI controller on %s [%s] using %s\n", mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)), (host->flags & SDHCI_USE_ADMA) ? @@ -3719,6 +3966,352 @@ EXPORT_SYMBOL_GPL(sdhci_free_host); + +/*****************************************************************************\ + * * + * Synchronous read/write functions for AVM * + * * +\*****************************************************************************/ + +#ifdef CONFIG_TFFS_PANIC_LOG +static int sdhci_poll_reg32_for_mask(struct sdhci_host *host, int reg, u32 mask, + bool negate, u32 *value) +{ + /* Be generous: Wait max 50 ms. */ + int timeout = 50; + int ret = 0; + u32 val; + + for (;;) { + val = sdhci_readl(host, reg); + if (!(val & mask) != !negate) + break; + if (timeout-- == 0) { + ret = -ETIMEDOUT; + break; + } + mdelay(1); + } + + if (value) + *value = val; + + if (ret) { + pr_err("%s: Error polling reg 0x%02x (0x%08x) for %smask 0x%08x: %d\n", + mmc_hostname(host->mmc), reg, val, negate ? "!" : "", + mask, ret); + sdhci_dumpregs(host); + } + + return ret; +} + + +#define avm_force_spin_lock_irqsave(lock, flags, waitms) ({ \ + unsigned w = (waitms); \ + bool locked; \ + for (;;) { \ + locked = spin_trylock_irqsave(lock, flags); \ + if (locked || --w == 0) \ + break; \ + mdelay(1); \ + } \ + if (!locked) { \ + pr_warning("%s: mmc host is locked, busting lock\n", __func__); \ + preempt_disable(); \ + local_irq_save(flags); \ + } \ + locked; \ +}) +#define avm_force_spin_unlock_irqrestore(lock, flags, locked) do { \ + if (locked) { \ + spin_unlock_irqrestore(lock, flags); \ + } else { \ + local_irq_restore(flags); \ + preempt_enable(); \ + } \ +} while (0) + +static int avm_sdhci_sync_io_init(struct mmc_host *mmc) +{ + struct sdhci_host *host = mmc_priv(mmc); + unsigned long flags; + bool havelock; + + if (!host) + return -ENODEV; + + pr_debug("%s: Initializing synchronous I/O for panic log\n", + mmc_hostname(mmc)); + + hw_mutex_jam_lock(HW_MUTEX_EMMC); + + disable_irq_nosync(host->irq); + havelock = avm_force_spin_lock_irqsave(&host->lock, flags, 200); + + sdhci_dumpregs(host); + + host->flags |= SDHCI_PANIC_SYNC; + + /* + * Clean up complicated stuff we cannot use in this context anyways: + * We use neither DMA, IRQs nor timers. + */ + + host->flags &= ~(SDHCI_USE_SDMA | SDHCI_USE_ADMA | SDHCI_REQ_USE_DMA | + SDHCI_DEVICE_DEAD); + if (host->version >= SDHCI_SPEC_300) + host->flags |= SDHCI_AUTO_CMD23; + + sdhci_reinit(host, 1); + + /* + * There is no safe way to disable the mmc tuning timer. + * We just ignore it and hope the best. + */ + + sdhci_enable_sdio_irq_nolock(host, false); + del_timer(&host->timer); + + avm_force_spin_unlock_irqrestore(&host->lock, flags, havelock); + + return 0; +} + +/* Debug */ +#define sdhci_poll_reg32_for_mask(...) ({ \ + int __ret = sdhci_poll_reg32_for_mask(__VA_ARGS__); \ + if (__ret) \ + pr_err("%s:%d: sdhci_poll_reg32_for_mask() -> %d\n", \ + __func__, __LINE__, __ret); \ + __ret; \ +}) + +static bool sdhci_card_cmd23(struct mmc_card *card) +{ + return mmc_host_cmd23(card->host) && + (mmc_card_mmc(card) || + (mmc_card_sd(card) && (card->scr.cmds & SD_SCR_CMD23_SUPPORT))); +} + +static int avm_sdhci_sync_io_blocks(struct mmc_host *mmc, sector_t blk, + size_t len, u8 *bufto, const u8 *buffrom) +{ + u16 xfermode, opcode, bufready, bufreadynow; + struct sdhci_host *host = mmc_priv(mmc); + struct mmc_card *card = mmc->card; + struct mmc_command cmd = {0}; + struct mmc_data data = {0}; + const size_t blksz = 512; + unsigned long flags; + unsigned blkcnt; + unsigned timeout; + size_t xferred; + u32 cardstatus; + bool havelock; + u32 intmask; + u8 hostctrl; + int ret; + int i; + + /* Partial block reads are emulated, writes are not */ + if (!host || (buffrom && (len % blksz)) || !bufto == !buffrom) { + WARN_ON(1); + return !host ? -ENODEV : -EINVAL; + } + if (!len) + return 0; + + blkcnt = DIV_ROUND_UP(len, blksz); + + pr_debug("%s: Sync %s, %zu bytes at block %llu\n", + mmc_hostname(host->mmc), + bufto ? "read" : "write", + len, (unsigned long long)blk); + + havelock = avm_force_spin_lock_irqsave(&host->lock, flags, 100); + + ret = sdhci_poll_reg32_for_mask(host, SDHCI_PRESENT_STATE, + SDHCI_STATE_BUSY_MASK, true, NULL); + if (ret) + goto out; + + /* Disable DMA selection */ + if (host->version >= SDHCI_SPEC_200) { + hostctrl = sdhci_readb(host, SDHCI_HOST_CONTROL); + hostctrl &= ~SDHCI_CTRL_DMA_MASK; + sdhci_writeb(host, hostctrl, SDHCI_HOST_CONTROL); + } + + sdhci_clock_gating(host, true); + sdhci_set_transfer_irqs(host); + + xfermode = SDHCI_TRNS_BLK_CNT_EN | SDHCI_TRNS_MULTI; + if ((host->flags & SDHCI_AUTO_CMD23) && sdhci_card_cmd23(card)) { + pr_debug("%s: Enabling Auto-CMD23 for transfer\n", + mmc_hostname(host->mmc)); + xfermode |= SDHCI_TRNS_AUTO_CMD23; + } else { + xfermode |= SDHCI_TRNS_AUTO_CMD12; + } + + if (bufto) { + /* Reading from device */ + xfermode |= SDHCI_TRNS_READ; + opcode = MMC_READ_MULTIPLE_BLOCK; + bufreadynow = SDHCI_INT_DATA_AVAIL; + bufready = SDHCI_DATA_AVAILABLE; + } else { + /* Writing to device */ + opcode = MMC_WRITE_MULTIPLE_BLOCK; + bufreadynow = SDHCI_INT_SPACE_AVAIL; + bufready = SDHCI_SPACE_AVAILABLE; + } + + /* + * Setup the minimum data so that we can leverage the existing + * functions to calculate the timeout + */ + data.flags = bufto ? MMC_DATA_WRITE : MMC_DATA_READ; + mmc_set_data_timeout(&data, card); + + timeout = data.timeout_ns / 1000; + if (host->clock) + timeout += data.timeout_clks / host->clock; + pr_debug("%s: mmc card timeout: %ums\n", mmc_hostname(host->mmc), + timeout / 1000); + + /* Translate timeout and write to timeout control register */ + cmd.data = &data; + cmd.opcode = opcode; + timeout = sdhci_calc_timeout(host, &cmd); + sdhci_writeb(host, timeout, SDHCI_TIMEOUT_CONTROL); + + sdhci_writew(host, SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG, + blksz), SDHCI_BLOCK_SIZE); + sdhci_writew(host, len / blksz, SDHCI_BLOCK_COUNT); + + sdhci_writel(host, mmc_card_blockaddr(card) ? blk : blk << 9, + SDHCI_ARGUMENT); + + if (xfermode & SDHCI_TRNS_AUTO_CMD23) + sdhci_writel(host, blkcnt, SDHCI_ARGUMENT2); + sdhci_writew(host, xfermode, SDHCI_TRANSFER_MODE); + sdhci_writew(host, + SDHCI_MAKE_CMD(opcode, + (SDHCI_CMD_RESP_SHORT | SDHCI_CMD_CRC | + SDHCI_CMD_INDEX | SDHCI_CMD_DATA)), + SDHCI_COMMAND); + + ret = sdhci_poll_reg32_for_mask(host, SDHCI_INT_STATUS, + SDHCI_INT_CMD_MASK, false, &intmask); + if (intmask & SDHCI_INT_ERROR_MASK) { + pr_err("Error issuing command to %s, status = 0x%08x\n", + mmc_hostname(host->mmc), intmask); + ret = -EIO; + goto out; + } + if (ret) + goto out; + sdhci_writel(host, intmask & SDHCI_INT_CMD_MASK, + SDHCI_INT_STATUS); + + cardstatus = sdhci_readl(host, SDHCI_RESPONSE); + if (R1_STATUS(cardstatus)) { + pr_err("Error response to command sent to %s, card status 0x%08x\n", + mmc_hostname(host->mmc), cardstatus); + ret = -EIO; + goto out; + } + + sdhci_writel(host, SDHCI_INT_DATA_MASK, SDHCI_INT_STATUS); + + for (xferred = 0; xferred < len;) { + if (!(sdhci_readl(host, SDHCI_PRESENT_STATE) & bufready)) { + ret = sdhci_poll_reg32_for_mask(host, SDHCI_INT_STATUS, + SDHCI_INT_DATA_MASK, false, &intmask); + if ((intmask & SDHCI_INT_ERROR_MASK) || + !(intmask & bufreadynow)) { + pr_err("%s: %s error, intstatus = 0x%08x\n", + mmc_hostname(host->mmc), + bufto ? "read" : "write", + intmask); + ret = -EIO; + goto out; + } + if (ret) + goto out; + sdhci_writel(host, intmask & SDHCI_INT_DATA_MASK, + SDHCI_INT_STATUS); + } + + if (host->quirks & SDHCI_QUIRK_PIO_NEEDS_DELAY) + udelay(100); + + /* Read/write one block */ + for (i = blksz/4; i; --i) { + u32 scratch; + int j; + + if (bufto) { + scratch = sdhci_readl(host, SDHCI_BUFFER); + + /* Discard excess bytes on partial block read */ + if (xferred == len) + continue; + + for (j = 0; j < 4; ++j) { + *bufto++ = scratch & 0xff; + scratch >>= 8; + if (++xferred == len) + break; + } + } else { + scratch = 0; + for (j = 0; j < 4; ++j) + scratch |= (u32)(*buffrom++) << j*8; + sdhci_writel(host, scratch, SDHCI_BUFFER); + xferred += 4; + } + } + } + + ret = sdhci_poll_reg32_for_mask(host, SDHCI_INT_STATUS, + SDHCI_INT_DATA_END | SDHCI_INT_ERROR_MASK, false, &intmask); + if (ret) + goto out; + if (intmask & SDHCI_INT_ERROR_MASK) { + pr_err("%s: %s error, intstatus = 0x%08x\n", + mmc_hostname(host->mmc), + bufto ? "read" : "write", + intmask); + ret = -EIO; + goto out; + } + sdhci_writel(host, intmask & SDHCI_INT_DATA_MASK, SDHCI_INT_STATUS); + + ret = 0; +out: + cardstatus = sdhci_readl(host, SDHCI_RESPONSE); + if (R1_STATUS(cardstatus)) { + pr_err("%s: Got error response, card status 0x%08x\n", + mmc_hostname(host->mmc), cardstatus); + if (!ret) + ret = -EIO; + } + + if (host->quirks & SDHCI_QUIRK_RESET_AFTER_REQUEST) { + sdhci_reset(host, SDHCI_RESET_CMD); + sdhci_reset(host, SDHCI_RESET_DATA); + } + + mmiowb(); + avm_force_spin_unlock_irqrestore(&host->lock, flags, havelock); + + return ret; +} +#endif + /*****************************************************************************\ * * * Driver init/exit *