18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  linux/drivers/mmc/host/wbsd.c - Winbond W83L51xD SD/MMC driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *  Copyright (C) 2004-2007 Pierre Ossman, All Rights Reserved.
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Warning!
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci * Changes to the FIFO system should be done with extreme care since
108c2ecf20Sopenharmony_ci * the hardware is full of bugs related to the FIFO. Known issues are:
118c2ecf20Sopenharmony_ci *
128c2ecf20Sopenharmony_ci * - FIFO size field in FSR is always zero.
138c2ecf20Sopenharmony_ci *
148c2ecf20Sopenharmony_ci * - FIFO interrupts tend not to work as they should. Interrupts are
158c2ecf20Sopenharmony_ci *   triggered only for full/empty events, not for threshold values.
168c2ecf20Sopenharmony_ci *
178c2ecf20Sopenharmony_ci * - On APIC systems the FIFO empty interrupt is sometimes lost.
188c2ecf20Sopenharmony_ci */
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#include <linux/module.h>
218c2ecf20Sopenharmony_ci#include <linux/moduleparam.h>
228c2ecf20Sopenharmony_ci#include <linux/init.h>
238c2ecf20Sopenharmony_ci#include <linux/ioport.h>
248c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
258c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
268c2ecf20Sopenharmony_ci#include <linux/dma-mapping.h>
278c2ecf20Sopenharmony_ci#include <linux/delay.h>
288c2ecf20Sopenharmony_ci#include <linux/pnp.h>
298c2ecf20Sopenharmony_ci#include <linux/highmem.h>
308c2ecf20Sopenharmony_ci#include <linux/mmc/host.h>
318c2ecf20Sopenharmony_ci#include <linux/mmc/mmc.h>
328c2ecf20Sopenharmony_ci#include <linux/mmc/sd.h>
338c2ecf20Sopenharmony_ci#include <linux/scatterlist.h>
348c2ecf20Sopenharmony_ci#include <linux/slab.h>
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci#include <asm/io.h>
378c2ecf20Sopenharmony_ci#include <asm/dma.h>
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci#include "wbsd.h"
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci#define DRIVER_NAME "wbsd"
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci#define DBG(x...) \
448c2ecf20Sopenharmony_ci	pr_debug(DRIVER_NAME ": " x)
458c2ecf20Sopenharmony_ci#define DBGF(f, x...) \
468c2ecf20Sopenharmony_ci	pr_debug(DRIVER_NAME " [%s()]: " f, __func__ , ##x)
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci/*
498c2ecf20Sopenharmony_ci * Device resources
508c2ecf20Sopenharmony_ci */
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci#ifdef CONFIG_PNP
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_cistatic const struct pnp_device_id pnp_dev_table[] = {
558c2ecf20Sopenharmony_ci	{ "WEC0517", 0 },
568c2ecf20Sopenharmony_ci	{ "WEC0518", 0 },
578c2ecf20Sopenharmony_ci	{ "", 0 },
588c2ecf20Sopenharmony_ci};
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pnp, pnp_dev_table);
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci#endif /* CONFIG_PNP */
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_cistatic const int config_ports[] = { 0x2E, 0x4E };
658c2ecf20Sopenharmony_cistatic const int unlock_codes[] = { 0x83, 0x87 };
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_cistatic const int valid_ids[] = {
688c2ecf20Sopenharmony_ci	0x7112,
698c2ecf20Sopenharmony_ci};
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci#ifdef CONFIG_PNP
728c2ecf20Sopenharmony_cistatic unsigned int param_nopnp = 0;
738c2ecf20Sopenharmony_ci#else
748c2ecf20Sopenharmony_cistatic const unsigned int param_nopnp = 1;
758c2ecf20Sopenharmony_ci#endif
768c2ecf20Sopenharmony_cistatic unsigned int param_io = 0x248;
778c2ecf20Sopenharmony_cistatic unsigned int param_irq = 6;
788c2ecf20Sopenharmony_cistatic int param_dma = 2;
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci/*
818c2ecf20Sopenharmony_ci * Basic functions
828c2ecf20Sopenharmony_ci */
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_cistatic inline void wbsd_unlock_config(struct wbsd_host *host)
858c2ecf20Sopenharmony_ci{
868c2ecf20Sopenharmony_ci	BUG_ON(host->config == 0);
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	outb(host->unlock_code, host->config);
898c2ecf20Sopenharmony_ci	outb(host->unlock_code, host->config);
908c2ecf20Sopenharmony_ci}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_cistatic inline void wbsd_lock_config(struct wbsd_host *host)
938c2ecf20Sopenharmony_ci{
948c2ecf20Sopenharmony_ci	BUG_ON(host->config == 0);
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	outb(LOCK_CODE, host->config);
978c2ecf20Sopenharmony_ci}
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_cistatic inline void wbsd_write_config(struct wbsd_host *host, u8 reg, u8 value)
1008c2ecf20Sopenharmony_ci{
1018c2ecf20Sopenharmony_ci	BUG_ON(host->config == 0);
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	outb(reg, host->config);
1048c2ecf20Sopenharmony_ci	outb(value, host->config + 1);
1058c2ecf20Sopenharmony_ci}
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_cistatic inline u8 wbsd_read_config(struct wbsd_host *host, u8 reg)
1088c2ecf20Sopenharmony_ci{
1098c2ecf20Sopenharmony_ci	BUG_ON(host->config == 0);
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	outb(reg, host->config);
1128c2ecf20Sopenharmony_ci	return inb(host->config + 1);
1138c2ecf20Sopenharmony_ci}
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_cistatic inline void wbsd_write_index(struct wbsd_host *host, u8 index, u8 value)
1168c2ecf20Sopenharmony_ci{
1178c2ecf20Sopenharmony_ci	outb(index, host->base + WBSD_IDXR);
1188c2ecf20Sopenharmony_ci	outb(value, host->base + WBSD_DATAR);
1198c2ecf20Sopenharmony_ci}
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_cistatic inline u8 wbsd_read_index(struct wbsd_host *host, u8 index)
1228c2ecf20Sopenharmony_ci{
1238c2ecf20Sopenharmony_ci	outb(index, host->base + WBSD_IDXR);
1248c2ecf20Sopenharmony_ci	return inb(host->base + WBSD_DATAR);
1258c2ecf20Sopenharmony_ci}
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci/*
1288c2ecf20Sopenharmony_ci * Common routines
1298c2ecf20Sopenharmony_ci */
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_cistatic void wbsd_init_device(struct wbsd_host *host)
1328c2ecf20Sopenharmony_ci{
1338c2ecf20Sopenharmony_ci	u8 setup, ier;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	/*
1368c2ecf20Sopenharmony_ci	 * Reset chip (SD/MMC part) and fifo.
1378c2ecf20Sopenharmony_ci	 */
1388c2ecf20Sopenharmony_ci	setup = wbsd_read_index(host, WBSD_IDX_SETUP);
1398c2ecf20Sopenharmony_ci	setup |= WBSD_FIFO_RESET | WBSD_SOFT_RESET;
1408c2ecf20Sopenharmony_ci	wbsd_write_index(host, WBSD_IDX_SETUP, setup);
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	/*
1438c2ecf20Sopenharmony_ci	 * Set DAT3 to input
1448c2ecf20Sopenharmony_ci	 */
1458c2ecf20Sopenharmony_ci	setup &= ~WBSD_DAT3_H;
1468c2ecf20Sopenharmony_ci	wbsd_write_index(host, WBSD_IDX_SETUP, setup);
1478c2ecf20Sopenharmony_ci	host->flags &= ~WBSD_FIGNORE_DETECT;
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	/*
1508c2ecf20Sopenharmony_ci	 * Read back default clock.
1518c2ecf20Sopenharmony_ci	 */
1528c2ecf20Sopenharmony_ci	host->clk = wbsd_read_index(host, WBSD_IDX_CLK);
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	/*
1558c2ecf20Sopenharmony_ci	 * Power down port.
1568c2ecf20Sopenharmony_ci	 */
1578c2ecf20Sopenharmony_ci	outb(WBSD_POWER_N, host->base + WBSD_CSR);
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci	/*
1608c2ecf20Sopenharmony_ci	 * Set maximum timeout.
1618c2ecf20Sopenharmony_ci	 */
1628c2ecf20Sopenharmony_ci	wbsd_write_index(host, WBSD_IDX_TAAC, 0x7F);
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	/*
1658c2ecf20Sopenharmony_ci	 * Test for card presence
1668c2ecf20Sopenharmony_ci	 */
1678c2ecf20Sopenharmony_ci	if (inb(host->base + WBSD_CSR) & WBSD_CARDPRESENT)
1688c2ecf20Sopenharmony_ci		host->flags |= WBSD_FCARD_PRESENT;
1698c2ecf20Sopenharmony_ci	else
1708c2ecf20Sopenharmony_ci		host->flags &= ~WBSD_FCARD_PRESENT;
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	/*
1738c2ecf20Sopenharmony_ci	 * Enable interesting interrupts.
1748c2ecf20Sopenharmony_ci	 */
1758c2ecf20Sopenharmony_ci	ier = 0;
1768c2ecf20Sopenharmony_ci	ier |= WBSD_EINT_CARD;
1778c2ecf20Sopenharmony_ci	ier |= WBSD_EINT_FIFO_THRE;
1788c2ecf20Sopenharmony_ci	ier |= WBSD_EINT_CRC;
1798c2ecf20Sopenharmony_ci	ier |= WBSD_EINT_TIMEOUT;
1808c2ecf20Sopenharmony_ci	ier |= WBSD_EINT_TC;
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	outb(ier, host->base + WBSD_EIR);
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	/*
1858c2ecf20Sopenharmony_ci	 * Clear interrupts.
1868c2ecf20Sopenharmony_ci	 */
1878c2ecf20Sopenharmony_ci	inb(host->base + WBSD_ISR);
1888c2ecf20Sopenharmony_ci}
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_cistatic void wbsd_reset(struct wbsd_host *host)
1918c2ecf20Sopenharmony_ci{
1928c2ecf20Sopenharmony_ci	u8 setup;
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci	pr_err("%s: Resetting chip\n", mmc_hostname(host->mmc));
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	/*
1978c2ecf20Sopenharmony_ci	 * Soft reset of chip (SD/MMC part).
1988c2ecf20Sopenharmony_ci	 */
1998c2ecf20Sopenharmony_ci	setup = wbsd_read_index(host, WBSD_IDX_SETUP);
2008c2ecf20Sopenharmony_ci	setup |= WBSD_SOFT_RESET;
2018c2ecf20Sopenharmony_ci	wbsd_write_index(host, WBSD_IDX_SETUP, setup);
2028c2ecf20Sopenharmony_ci}
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_cistatic void wbsd_request_end(struct wbsd_host *host, struct mmc_request *mrq)
2058c2ecf20Sopenharmony_ci{
2068c2ecf20Sopenharmony_ci	unsigned long dmaflags;
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci	if (host->dma >= 0) {
2098c2ecf20Sopenharmony_ci		/*
2108c2ecf20Sopenharmony_ci		 * Release ISA DMA controller.
2118c2ecf20Sopenharmony_ci		 */
2128c2ecf20Sopenharmony_ci		dmaflags = claim_dma_lock();
2138c2ecf20Sopenharmony_ci		disable_dma(host->dma);
2148c2ecf20Sopenharmony_ci		clear_dma_ff(host->dma);
2158c2ecf20Sopenharmony_ci		release_dma_lock(dmaflags);
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci		/*
2188c2ecf20Sopenharmony_ci		 * Disable DMA on host.
2198c2ecf20Sopenharmony_ci		 */
2208c2ecf20Sopenharmony_ci		wbsd_write_index(host, WBSD_IDX_DMA, 0);
2218c2ecf20Sopenharmony_ci	}
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci	host->mrq = NULL;
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci	/*
2268c2ecf20Sopenharmony_ci	 * MMC layer might call back into the driver so first unlock.
2278c2ecf20Sopenharmony_ci	 */
2288c2ecf20Sopenharmony_ci	spin_unlock(&host->lock);
2298c2ecf20Sopenharmony_ci	mmc_request_done(host->mmc, mrq);
2308c2ecf20Sopenharmony_ci	spin_lock(&host->lock);
2318c2ecf20Sopenharmony_ci}
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci/*
2348c2ecf20Sopenharmony_ci * Scatter/gather functions
2358c2ecf20Sopenharmony_ci */
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_cistatic inline void wbsd_init_sg(struct wbsd_host *host, struct mmc_data *data)
2388c2ecf20Sopenharmony_ci{
2398c2ecf20Sopenharmony_ci	/*
2408c2ecf20Sopenharmony_ci	 * Get info. about SG list from data structure.
2418c2ecf20Sopenharmony_ci	 */
2428c2ecf20Sopenharmony_ci	host->cur_sg = data->sg;
2438c2ecf20Sopenharmony_ci	host->num_sg = data->sg_len;
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci	host->offset = 0;
2468c2ecf20Sopenharmony_ci	host->remain = host->cur_sg->length;
2478c2ecf20Sopenharmony_ci}
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_cistatic inline int wbsd_next_sg(struct wbsd_host *host)
2508c2ecf20Sopenharmony_ci{
2518c2ecf20Sopenharmony_ci	/*
2528c2ecf20Sopenharmony_ci	 * Skip to next SG entry.
2538c2ecf20Sopenharmony_ci	 */
2548c2ecf20Sopenharmony_ci	host->cur_sg++;
2558c2ecf20Sopenharmony_ci	host->num_sg--;
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_ci	/*
2588c2ecf20Sopenharmony_ci	 * Any entries left?
2598c2ecf20Sopenharmony_ci	 */
2608c2ecf20Sopenharmony_ci	if (host->num_sg > 0) {
2618c2ecf20Sopenharmony_ci		host->offset = 0;
2628c2ecf20Sopenharmony_ci		host->remain = host->cur_sg->length;
2638c2ecf20Sopenharmony_ci	}
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_ci	return host->num_sg;
2668c2ecf20Sopenharmony_ci}
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_cistatic inline char *wbsd_map_sg(struct wbsd_host *host)
2698c2ecf20Sopenharmony_ci{
2708c2ecf20Sopenharmony_ci	return kmap_atomic(sg_page(host->cur_sg)) + host->cur_sg->offset;
2718c2ecf20Sopenharmony_ci}
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_cistatic inline void wbsd_sg_to_dma(struct wbsd_host *host, struct mmc_data *data)
2748c2ecf20Sopenharmony_ci{
2758c2ecf20Sopenharmony_ci	size_t len = 0;
2768c2ecf20Sopenharmony_ci	int i;
2778c2ecf20Sopenharmony_ci
2788c2ecf20Sopenharmony_ci	for (i = 0; i < data->sg_len; i++)
2798c2ecf20Sopenharmony_ci		len += data->sg[i].length;
2808c2ecf20Sopenharmony_ci	sg_copy_to_buffer(data->sg, data->sg_len, host->dma_buffer, len);
2818c2ecf20Sopenharmony_ci}
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_cistatic inline void wbsd_dma_to_sg(struct wbsd_host *host, struct mmc_data *data)
2848c2ecf20Sopenharmony_ci{
2858c2ecf20Sopenharmony_ci	size_t len = 0;
2868c2ecf20Sopenharmony_ci	int i;
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_ci	for (i = 0; i < data->sg_len; i++)
2898c2ecf20Sopenharmony_ci		len += data->sg[i].length;
2908c2ecf20Sopenharmony_ci	sg_copy_from_buffer(data->sg, data->sg_len, host->dma_buffer, len);
2918c2ecf20Sopenharmony_ci}
2928c2ecf20Sopenharmony_ci
2938c2ecf20Sopenharmony_ci/*
2948c2ecf20Sopenharmony_ci * Command handling
2958c2ecf20Sopenharmony_ci */
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_cistatic inline void wbsd_get_short_reply(struct wbsd_host *host,
2988c2ecf20Sopenharmony_ci					struct mmc_command *cmd)
2998c2ecf20Sopenharmony_ci{
3008c2ecf20Sopenharmony_ci	/*
3018c2ecf20Sopenharmony_ci	 * Correct response type?
3028c2ecf20Sopenharmony_ci	 */
3038c2ecf20Sopenharmony_ci	if (wbsd_read_index(host, WBSD_IDX_RSPLEN) != WBSD_RSP_SHORT) {
3048c2ecf20Sopenharmony_ci		cmd->error = -EILSEQ;
3058c2ecf20Sopenharmony_ci		return;
3068c2ecf20Sopenharmony_ci	}
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ci	cmd->resp[0]  = wbsd_read_index(host, WBSD_IDX_RESP12) << 24;
3098c2ecf20Sopenharmony_ci	cmd->resp[0] |= wbsd_read_index(host, WBSD_IDX_RESP13) << 16;
3108c2ecf20Sopenharmony_ci	cmd->resp[0] |= wbsd_read_index(host, WBSD_IDX_RESP14) << 8;
3118c2ecf20Sopenharmony_ci	cmd->resp[0] |= wbsd_read_index(host, WBSD_IDX_RESP15) << 0;
3128c2ecf20Sopenharmony_ci	cmd->resp[1]  = wbsd_read_index(host, WBSD_IDX_RESP16) << 24;
3138c2ecf20Sopenharmony_ci}
3148c2ecf20Sopenharmony_ci
3158c2ecf20Sopenharmony_cistatic inline void wbsd_get_long_reply(struct wbsd_host *host,
3168c2ecf20Sopenharmony_ci	struct mmc_command *cmd)
3178c2ecf20Sopenharmony_ci{
3188c2ecf20Sopenharmony_ci	int i;
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_ci	/*
3218c2ecf20Sopenharmony_ci	 * Correct response type?
3228c2ecf20Sopenharmony_ci	 */
3238c2ecf20Sopenharmony_ci	if (wbsd_read_index(host, WBSD_IDX_RSPLEN) != WBSD_RSP_LONG) {
3248c2ecf20Sopenharmony_ci		cmd->error = -EILSEQ;
3258c2ecf20Sopenharmony_ci		return;
3268c2ecf20Sopenharmony_ci	}
3278c2ecf20Sopenharmony_ci
3288c2ecf20Sopenharmony_ci	for (i = 0; i < 4; i++) {
3298c2ecf20Sopenharmony_ci		cmd->resp[i] =
3308c2ecf20Sopenharmony_ci			wbsd_read_index(host, WBSD_IDX_RESP1 + i * 4) << 24;
3318c2ecf20Sopenharmony_ci		cmd->resp[i] |=
3328c2ecf20Sopenharmony_ci			wbsd_read_index(host, WBSD_IDX_RESP2 + i * 4) << 16;
3338c2ecf20Sopenharmony_ci		cmd->resp[i] |=
3348c2ecf20Sopenharmony_ci			wbsd_read_index(host, WBSD_IDX_RESP3 + i * 4) << 8;
3358c2ecf20Sopenharmony_ci		cmd->resp[i] |=
3368c2ecf20Sopenharmony_ci			wbsd_read_index(host, WBSD_IDX_RESP4 + i * 4) << 0;
3378c2ecf20Sopenharmony_ci	}
3388c2ecf20Sopenharmony_ci}
3398c2ecf20Sopenharmony_ci
3408c2ecf20Sopenharmony_cistatic void wbsd_send_command(struct wbsd_host *host, struct mmc_command *cmd)
3418c2ecf20Sopenharmony_ci{
3428c2ecf20Sopenharmony_ci	int i;
3438c2ecf20Sopenharmony_ci	u8 status, isr;
3448c2ecf20Sopenharmony_ci
3458c2ecf20Sopenharmony_ci	/*
3468c2ecf20Sopenharmony_ci	 * Clear accumulated ISR. The interrupt routine
3478c2ecf20Sopenharmony_ci	 * will fill this one with events that occur during
3488c2ecf20Sopenharmony_ci	 * transfer.
3498c2ecf20Sopenharmony_ci	 */
3508c2ecf20Sopenharmony_ci	host->isr = 0;
3518c2ecf20Sopenharmony_ci
3528c2ecf20Sopenharmony_ci	/*
3538c2ecf20Sopenharmony_ci	 * Send the command (CRC calculated by host).
3548c2ecf20Sopenharmony_ci	 */
3558c2ecf20Sopenharmony_ci	outb(cmd->opcode, host->base + WBSD_CMDR);
3568c2ecf20Sopenharmony_ci	for (i = 3; i >= 0; i--)
3578c2ecf20Sopenharmony_ci		outb((cmd->arg >> (i * 8)) & 0xff, host->base + WBSD_CMDR);
3588c2ecf20Sopenharmony_ci
3598c2ecf20Sopenharmony_ci	cmd->error = 0;
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_ci	/*
3628c2ecf20Sopenharmony_ci	 * Wait for the request to complete.
3638c2ecf20Sopenharmony_ci	 */
3648c2ecf20Sopenharmony_ci	do {
3658c2ecf20Sopenharmony_ci		status = wbsd_read_index(host, WBSD_IDX_STATUS);
3668c2ecf20Sopenharmony_ci	} while (status & WBSD_CARDTRAFFIC);
3678c2ecf20Sopenharmony_ci
3688c2ecf20Sopenharmony_ci	/*
3698c2ecf20Sopenharmony_ci	 * Do we expect a reply?
3708c2ecf20Sopenharmony_ci	 */
3718c2ecf20Sopenharmony_ci	if (cmd->flags & MMC_RSP_PRESENT) {
3728c2ecf20Sopenharmony_ci		/*
3738c2ecf20Sopenharmony_ci		 * Read back status.
3748c2ecf20Sopenharmony_ci		 */
3758c2ecf20Sopenharmony_ci		isr = host->isr;
3768c2ecf20Sopenharmony_ci
3778c2ecf20Sopenharmony_ci		/* Card removed? */
3788c2ecf20Sopenharmony_ci		if (isr & WBSD_INT_CARD)
3798c2ecf20Sopenharmony_ci			cmd->error = -ENOMEDIUM;
3808c2ecf20Sopenharmony_ci		/* Timeout? */
3818c2ecf20Sopenharmony_ci		else if (isr & WBSD_INT_TIMEOUT)
3828c2ecf20Sopenharmony_ci			cmd->error = -ETIMEDOUT;
3838c2ecf20Sopenharmony_ci		/* CRC? */
3848c2ecf20Sopenharmony_ci		else if ((cmd->flags & MMC_RSP_CRC) && (isr & WBSD_INT_CRC))
3858c2ecf20Sopenharmony_ci			cmd->error = -EILSEQ;
3868c2ecf20Sopenharmony_ci		/* All ok */
3878c2ecf20Sopenharmony_ci		else {
3888c2ecf20Sopenharmony_ci			if (cmd->flags & MMC_RSP_136)
3898c2ecf20Sopenharmony_ci				wbsd_get_long_reply(host, cmd);
3908c2ecf20Sopenharmony_ci			else
3918c2ecf20Sopenharmony_ci				wbsd_get_short_reply(host, cmd);
3928c2ecf20Sopenharmony_ci		}
3938c2ecf20Sopenharmony_ci	}
3948c2ecf20Sopenharmony_ci}
3958c2ecf20Sopenharmony_ci
3968c2ecf20Sopenharmony_ci/*
3978c2ecf20Sopenharmony_ci * Data functions
3988c2ecf20Sopenharmony_ci */
3998c2ecf20Sopenharmony_ci
4008c2ecf20Sopenharmony_cistatic void wbsd_empty_fifo(struct wbsd_host *host)
4018c2ecf20Sopenharmony_ci{
4028c2ecf20Sopenharmony_ci	struct mmc_data *data = host->mrq->cmd->data;
4038c2ecf20Sopenharmony_ci	char *buffer;
4048c2ecf20Sopenharmony_ci	int i, idx, fsr, fifo;
4058c2ecf20Sopenharmony_ci
4068c2ecf20Sopenharmony_ci	/*
4078c2ecf20Sopenharmony_ci	 * Handle excessive data.
4088c2ecf20Sopenharmony_ci	 */
4098c2ecf20Sopenharmony_ci	if (host->num_sg == 0)
4108c2ecf20Sopenharmony_ci		return;
4118c2ecf20Sopenharmony_ci
4128c2ecf20Sopenharmony_ci	buffer = wbsd_map_sg(host) + host->offset;
4138c2ecf20Sopenharmony_ci	idx = 0;
4148c2ecf20Sopenharmony_ci
4158c2ecf20Sopenharmony_ci	/*
4168c2ecf20Sopenharmony_ci	 * Drain the fifo. This has a tendency to loop longer
4178c2ecf20Sopenharmony_ci	 * than the FIFO length (usually one block).
4188c2ecf20Sopenharmony_ci	 */
4198c2ecf20Sopenharmony_ci	while (!((fsr = inb(host->base + WBSD_FSR)) & WBSD_FIFO_EMPTY)) {
4208c2ecf20Sopenharmony_ci		/*
4218c2ecf20Sopenharmony_ci		 * The size field in the FSR is broken so we have to
4228c2ecf20Sopenharmony_ci		 * do some guessing.
4238c2ecf20Sopenharmony_ci		 */
4248c2ecf20Sopenharmony_ci		if (fsr & WBSD_FIFO_FULL)
4258c2ecf20Sopenharmony_ci			fifo = 16;
4268c2ecf20Sopenharmony_ci		else if (fsr & WBSD_FIFO_FUTHRE)
4278c2ecf20Sopenharmony_ci			fifo = 8;
4288c2ecf20Sopenharmony_ci		else
4298c2ecf20Sopenharmony_ci			fifo = 1;
4308c2ecf20Sopenharmony_ci
4318c2ecf20Sopenharmony_ci		for (i = 0; i < fifo; i++) {
4328c2ecf20Sopenharmony_ci			buffer[idx++] = inb(host->base + WBSD_DFR);
4338c2ecf20Sopenharmony_ci			host->offset++;
4348c2ecf20Sopenharmony_ci			host->remain--;
4358c2ecf20Sopenharmony_ci
4368c2ecf20Sopenharmony_ci			data->bytes_xfered++;
4378c2ecf20Sopenharmony_ci
4388c2ecf20Sopenharmony_ci			/*
4398c2ecf20Sopenharmony_ci			 * End of scatter list entry?
4408c2ecf20Sopenharmony_ci			 */
4418c2ecf20Sopenharmony_ci			if (host->remain == 0) {
4428c2ecf20Sopenharmony_ci				kunmap_atomic(buffer);
4438c2ecf20Sopenharmony_ci				/*
4448c2ecf20Sopenharmony_ci				 * Get next entry. Check if last.
4458c2ecf20Sopenharmony_ci				 */
4468c2ecf20Sopenharmony_ci				if (!wbsd_next_sg(host))
4478c2ecf20Sopenharmony_ci					return;
4488c2ecf20Sopenharmony_ci
4498c2ecf20Sopenharmony_ci				buffer = wbsd_map_sg(host);
4508c2ecf20Sopenharmony_ci				idx = 0;
4518c2ecf20Sopenharmony_ci			}
4528c2ecf20Sopenharmony_ci		}
4538c2ecf20Sopenharmony_ci	}
4548c2ecf20Sopenharmony_ci	kunmap_atomic(buffer);
4558c2ecf20Sopenharmony_ci
4568c2ecf20Sopenharmony_ci	/*
4578c2ecf20Sopenharmony_ci	 * This is a very dirty hack to solve a
4588c2ecf20Sopenharmony_ci	 * hardware problem. The chip doesn't trigger
4598c2ecf20Sopenharmony_ci	 * FIFO threshold interrupts properly.
4608c2ecf20Sopenharmony_ci	 */
4618c2ecf20Sopenharmony_ci	if ((data->blocks * data->blksz - data->bytes_xfered) < 16)
4628c2ecf20Sopenharmony_ci		tasklet_schedule(&host->fifo_tasklet);
4638c2ecf20Sopenharmony_ci}
4648c2ecf20Sopenharmony_ci
4658c2ecf20Sopenharmony_cistatic void wbsd_fill_fifo(struct wbsd_host *host)
4668c2ecf20Sopenharmony_ci{
4678c2ecf20Sopenharmony_ci	struct mmc_data *data = host->mrq->cmd->data;
4688c2ecf20Sopenharmony_ci	char *buffer;
4698c2ecf20Sopenharmony_ci	int i, idx, fsr, fifo;
4708c2ecf20Sopenharmony_ci
4718c2ecf20Sopenharmony_ci	/*
4728c2ecf20Sopenharmony_ci	 * Check that we aren't being called after the
4738c2ecf20Sopenharmony_ci	 * entire buffer has been transferred.
4748c2ecf20Sopenharmony_ci	 */
4758c2ecf20Sopenharmony_ci	if (host->num_sg == 0)
4768c2ecf20Sopenharmony_ci		return;
4778c2ecf20Sopenharmony_ci
4788c2ecf20Sopenharmony_ci	buffer = wbsd_map_sg(host) + host->offset;
4798c2ecf20Sopenharmony_ci	idx = 0;
4808c2ecf20Sopenharmony_ci
4818c2ecf20Sopenharmony_ci	/*
4828c2ecf20Sopenharmony_ci	 * Fill the fifo. This has a tendency to loop longer
4838c2ecf20Sopenharmony_ci	 * than the FIFO length (usually one block).
4848c2ecf20Sopenharmony_ci	 */
4858c2ecf20Sopenharmony_ci	while (!((fsr = inb(host->base + WBSD_FSR)) & WBSD_FIFO_FULL)) {
4868c2ecf20Sopenharmony_ci		/*
4878c2ecf20Sopenharmony_ci		 * The size field in the FSR is broken so we have to
4888c2ecf20Sopenharmony_ci		 * do some guessing.
4898c2ecf20Sopenharmony_ci		 */
4908c2ecf20Sopenharmony_ci		if (fsr & WBSD_FIFO_EMPTY)
4918c2ecf20Sopenharmony_ci			fifo = 0;
4928c2ecf20Sopenharmony_ci		else if (fsr & WBSD_FIFO_EMTHRE)
4938c2ecf20Sopenharmony_ci			fifo = 8;
4948c2ecf20Sopenharmony_ci		else
4958c2ecf20Sopenharmony_ci			fifo = 15;
4968c2ecf20Sopenharmony_ci
4978c2ecf20Sopenharmony_ci		for (i = 16; i > fifo; i--) {
4988c2ecf20Sopenharmony_ci			outb(buffer[idx], host->base + WBSD_DFR);
4998c2ecf20Sopenharmony_ci			host->offset++;
5008c2ecf20Sopenharmony_ci			host->remain--;
5018c2ecf20Sopenharmony_ci
5028c2ecf20Sopenharmony_ci			data->bytes_xfered++;
5038c2ecf20Sopenharmony_ci
5048c2ecf20Sopenharmony_ci			/*
5058c2ecf20Sopenharmony_ci			 * End of scatter list entry?
5068c2ecf20Sopenharmony_ci			 */
5078c2ecf20Sopenharmony_ci			if (host->remain == 0) {
5088c2ecf20Sopenharmony_ci				kunmap_atomic(buffer);
5098c2ecf20Sopenharmony_ci				/*
5108c2ecf20Sopenharmony_ci				 * Get next entry. Check if last.
5118c2ecf20Sopenharmony_ci				 */
5128c2ecf20Sopenharmony_ci				if (!wbsd_next_sg(host))
5138c2ecf20Sopenharmony_ci					return;
5148c2ecf20Sopenharmony_ci
5158c2ecf20Sopenharmony_ci				buffer = wbsd_map_sg(host);
5168c2ecf20Sopenharmony_ci				idx = 0;
5178c2ecf20Sopenharmony_ci			}
5188c2ecf20Sopenharmony_ci		}
5198c2ecf20Sopenharmony_ci	}
5208c2ecf20Sopenharmony_ci	kunmap_atomic(buffer);
5218c2ecf20Sopenharmony_ci
5228c2ecf20Sopenharmony_ci	/*
5238c2ecf20Sopenharmony_ci	 * The controller stops sending interrupts for
5248c2ecf20Sopenharmony_ci	 * 'FIFO empty' under certain conditions. So we
5258c2ecf20Sopenharmony_ci	 * need to be a bit more pro-active.
5268c2ecf20Sopenharmony_ci	 */
5278c2ecf20Sopenharmony_ci	tasklet_schedule(&host->fifo_tasklet);
5288c2ecf20Sopenharmony_ci}
5298c2ecf20Sopenharmony_ci
5308c2ecf20Sopenharmony_cistatic void wbsd_prepare_data(struct wbsd_host *host, struct mmc_data *data)
5318c2ecf20Sopenharmony_ci{
5328c2ecf20Sopenharmony_ci	u16 blksize;
5338c2ecf20Sopenharmony_ci	u8 setup;
5348c2ecf20Sopenharmony_ci	unsigned long dmaflags;
5358c2ecf20Sopenharmony_ci	unsigned int size;
5368c2ecf20Sopenharmony_ci
5378c2ecf20Sopenharmony_ci	/*
5388c2ecf20Sopenharmony_ci	 * Calculate size.
5398c2ecf20Sopenharmony_ci	 */
5408c2ecf20Sopenharmony_ci	size = data->blocks * data->blksz;
5418c2ecf20Sopenharmony_ci
5428c2ecf20Sopenharmony_ci	/*
5438c2ecf20Sopenharmony_ci	 * Check timeout values for overflow.
5448c2ecf20Sopenharmony_ci	 * (Yes, some cards cause this value to overflow).
5458c2ecf20Sopenharmony_ci	 */
5468c2ecf20Sopenharmony_ci	if (data->timeout_ns > 127000000)
5478c2ecf20Sopenharmony_ci		wbsd_write_index(host, WBSD_IDX_TAAC, 127);
5488c2ecf20Sopenharmony_ci	else {
5498c2ecf20Sopenharmony_ci		wbsd_write_index(host, WBSD_IDX_TAAC,
5508c2ecf20Sopenharmony_ci			data->timeout_ns / 1000000);
5518c2ecf20Sopenharmony_ci	}
5528c2ecf20Sopenharmony_ci
5538c2ecf20Sopenharmony_ci	if (data->timeout_clks > 255)
5548c2ecf20Sopenharmony_ci		wbsd_write_index(host, WBSD_IDX_NSAC, 255);
5558c2ecf20Sopenharmony_ci	else
5568c2ecf20Sopenharmony_ci		wbsd_write_index(host, WBSD_IDX_NSAC, data->timeout_clks);
5578c2ecf20Sopenharmony_ci
5588c2ecf20Sopenharmony_ci	/*
5598c2ecf20Sopenharmony_ci	 * Inform the chip of how large blocks will be
5608c2ecf20Sopenharmony_ci	 * sent. It needs this to determine when to
5618c2ecf20Sopenharmony_ci	 * calculate CRC.
5628c2ecf20Sopenharmony_ci	 *
5638c2ecf20Sopenharmony_ci	 * Space for CRC must be included in the size.
5648c2ecf20Sopenharmony_ci	 * Two bytes are needed for each data line.
5658c2ecf20Sopenharmony_ci	 */
5668c2ecf20Sopenharmony_ci	if (host->bus_width == MMC_BUS_WIDTH_1) {
5678c2ecf20Sopenharmony_ci		blksize = data->blksz + 2;
5688c2ecf20Sopenharmony_ci
5698c2ecf20Sopenharmony_ci		wbsd_write_index(host, WBSD_IDX_PBSMSB, (blksize >> 4) & 0xF0);
5708c2ecf20Sopenharmony_ci		wbsd_write_index(host, WBSD_IDX_PBSLSB, blksize & 0xFF);
5718c2ecf20Sopenharmony_ci	} else if (host->bus_width == MMC_BUS_WIDTH_4) {
5728c2ecf20Sopenharmony_ci		blksize = data->blksz + 2 * 4;
5738c2ecf20Sopenharmony_ci
5748c2ecf20Sopenharmony_ci		wbsd_write_index(host, WBSD_IDX_PBSMSB,
5758c2ecf20Sopenharmony_ci			((blksize >> 4) & 0xF0) | WBSD_DATA_WIDTH);
5768c2ecf20Sopenharmony_ci		wbsd_write_index(host, WBSD_IDX_PBSLSB, blksize & 0xFF);
5778c2ecf20Sopenharmony_ci	} else {
5788c2ecf20Sopenharmony_ci		data->error = -EINVAL;
5798c2ecf20Sopenharmony_ci		return;
5808c2ecf20Sopenharmony_ci	}
5818c2ecf20Sopenharmony_ci
5828c2ecf20Sopenharmony_ci	/*
5838c2ecf20Sopenharmony_ci	 * Clear the FIFO. This is needed even for DMA
5848c2ecf20Sopenharmony_ci	 * transfers since the chip still uses the FIFO
5858c2ecf20Sopenharmony_ci	 * internally.
5868c2ecf20Sopenharmony_ci	 */
5878c2ecf20Sopenharmony_ci	setup = wbsd_read_index(host, WBSD_IDX_SETUP);
5888c2ecf20Sopenharmony_ci	setup |= WBSD_FIFO_RESET;
5898c2ecf20Sopenharmony_ci	wbsd_write_index(host, WBSD_IDX_SETUP, setup);
5908c2ecf20Sopenharmony_ci
5918c2ecf20Sopenharmony_ci	/*
5928c2ecf20Sopenharmony_ci	 * DMA transfer?
5938c2ecf20Sopenharmony_ci	 */
5948c2ecf20Sopenharmony_ci	if (host->dma >= 0) {
5958c2ecf20Sopenharmony_ci		/*
5968c2ecf20Sopenharmony_ci		 * The buffer for DMA is only 64 kB.
5978c2ecf20Sopenharmony_ci		 */
5988c2ecf20Sopenharmony_ci		BUG_ON(size > 0x10000);
5998c2ecf20Sopenharmony_ci		if (size > 0x10000) {
6008c2ecf20Sopenharmony_ci			data->error = -EINVAL;
6018c2ecf20Sopenharmony_ci			return;
6028c2ecf20Sopenharmony_ci		}
6038c2ecf20Sopenharmony_ci
6048c2ecf20Sopenharmony_ci		/*
6058c2ecf20Sopenharmony_ci		 * Transfer data from the SG list to
6068c2ecf20Sopenharmony_ci		 * the DMA buffer.
6078c2ecf20Sopenharmony_ci		 */
6088c2ecf20Sopenharmony_ci		if (data->flags & MMC_DATA_WRITE)
6098c2ecf20Sopenharmony_ci			wbsd_sg_to_dma(host, data);
6108c2ecf20Sopenharmony_ci
6118c2ecf20Sopenharmony_ci		/*
6128c2ecf20Sopenharmony_ci		 * Initialise the ISA DMA controller.
6138c2ecf20Sopenharmony_ci		 */
6148c2ecf20Sopenharmony_ci		dmaflags = claim_dma_lock();
6158c2ecf20Sopenharmony_ci		disable_dma(host->dma);
6168c2ecf20Sopenharmony_ci		clear_dma_ff(host->dma);
6178c2ecf20Sopenharmony_ci		if (data->flags & MMC_DATA_READ)
6188c2ecf20Sopenharmony_ci			set_dma_mode(host->dma, DMA_MODE_READ & ~0x40);
6198c2ecf20Sopenharmony_ci		else
6208c2ecf20Sopenharmony_ci			set_dma_mode(host->dma, DMA_MODE_WRITE & ~0x40);
6218c2ecf20Sopenharmony_ci		set_dma_addr(host->dma, host->dma_addr);
6228c2ecf20Sopenharmony_ci		set_dma_count(host->dma, size);
6238c2ecf20Sopenharmony_ci
6248c2ecf20Sopenharmony_ci		enable_dma(host->dma);
6258c2ecf20Sopenharmony_ci		release_dma_lock(dmaflags);
6268c2ecf20Sopenharmony_ci
6278c2ecf20Sopenharmony_ci		/*
6288c2ecf20Sopenharmony_ci		 * Enable DMA on the host.
6298c2ecf20Sopenharmony_ci		 */
6308c2ecf20Sopenharmony_ci		wbsd_write_index(host, WBSD_IDX_DMA, WBSD_DMA_ENABLE);
6318c2ecf20Sopenharmony_ci	} else {
6328c2ecf20Sopenharmony_ci		/*
6338c2ecf20Sopenharmony_ci		 * This flag is used to keep printk
6348c2ecf20Sopenharmony_ci		 * output to a minimum.
6358c2ecf20Sopenharmony_ci		 */
6368c2ecf20Sopenharmony_ci		host->firsterr = 1;
6378c2ecf20Sopenharmony_ci
6388c2ecf20Sopenharmony_ci		/*
6398c2ecf20Sopenharmony_ci		 * Initialise the SG list.
6408c2ecf20Sopenharmony_ci		 */
6418c2ecf20Sopenharmony_ci		wbsd_init_sg(host, data);
6428c2ecf20Sopenharmony_ci
6438c2ecf20Sopenharmony_ci		/*
6448c2ecf20Sopenharmony_ci		 * Turn off DMA.
6458c2ecf20Sopenharmony_ci		 */
6468c2ecf20Sopenharmony_ci		wbsd_write_index(host, WBSD_IDX_DMA, 0);
6478c2ecf20Sopenharmony_ci
6488c2ecf20Sopenharmony_ci		/*
6498c2ecf20Sopenharmony_ci		 * Set up FIFO threshold levels (and fill
6508c2ecf20Sopenharmony_ci		 * buffer if doing a write).
6518c2ecf20Sopenharmony_ci		 */
6528c2ecf20Sopenharmony_ci		if (data->flags & MMC_DATA_READ) {
6538c2ecf20Sopenharmony_ci			wbsd_write_index(host, WBSD_IDX_FIFOEN,
6548c2ecf20Sopenharmony_ci				WBSD_FIFOEN_FULL | 8);
6558c2ecf20Sopenharmony_ci		} else {
6568c2ecf20Sopenharmony_ci			wbsd_write_index(host, WBSD_IDX_FIFOEN,
6578c2ecf20Sopenharmony_ci				WBSD_FIFOEN_EMPTY | 8);
6588c2ecf20Sopenharmony_ci			wbsd_fill_fifo(host);
6598c2ecf20Sopenharmony_ci		}
6608c2ecf20Sopenharmony_ci	}
6618c2ecf20Sopenharmony_ci
6628c2ecf20Sopenharmony_ci	data->error = 0;
6638c2ecf20Sopenharmony_ci}
6648c2ecf20Sopenharmony_ci
6658c2ecf20Sopenharmony_cistatic void wbsd_finish_data(struct wbsd_host *host, struct mmc_data *data)
6668c2ecf20Sopenharmony_ci{
6678c2ecf20Sopenharmony_ci	unsigned long dmaflags;
6688c2ecf20Sopenharmony_ci	int count;
6698c2ecf20Sopenharmony_ci	u8 status;
6708c2ecf20Sopenharmony_ci
6718c2ecf20Sopenharmony_ci	WARN_ON(host->mrq == NULL);
6728c2ecf20Sopenharmony_ci
6738c2ecf20Sopenharmony_ci	/*
6748c2ecf20Sopenharmony_ci	 * Send a stop command if needed.
6758c2ecf20Sopenharmony_ci	 */
6768c2ecf20Sopenharmony_ci	if (data->stop)
6778c2ecf20Sopenharmony_ci		wbsd_send_command(host, data->stop);
6788c2ecf20Sopenharmony_ci
6798c2ecf20Sopenharmony_ci	/*
6808c2ecf20Sopenharmony_ci	 * Wait for the controller to leave data
6818c2ecf20Sopenharmony_ci	 * transfer state.
6828c2ecf20Sopenharmony_ci	 */
6838c2ecf20Sopenharmony_ci	do {
6848c2ecf20Sopenharmony_ci		status = wbsd_read_index(host, WBSD_IDX_STATUS);
6858c2ecf20Sopenharmony_ci	} while (status & (WBSD_BLOCK_READ | WBSD_BLOCK_WRITE));
6868c2ecf20Sopenharmony_ci
6878c2ecf20Sopenharmony_ci	/*
6888c2ecf20Sopenharmony_ci	 * DMA transfer?
6898c2ecf20Sopenharmony_ci	 */
6908c2ecf20Sopenharmony_ci	if (host->dma >= 0) {
6918c2ecf20Sopenharmony_ci		/*
6928c2ecf20Sopenharmony_ci		 * Disable DMA on the host.
6938c2ecf20Sopenharmony_ci		 */
6948c2ecf20Sopenharmony_ci		wbsd_write_index(host, WBSD_IDX_DMA, 0);
6958c2ecf20Sopenharmony_ci
6968c2ecf20Sopenharmony_ci		/*
6978c2ecf20Sopenharmony_ci		 * Turn of ISA DMA controller.
6988c2ecf20Sopenharmony_ci		 */
6998c2ecf20Sopenharmony_ci		dmaflags = claim_dma_lock();
7008c2ecf20Sopenharmony_ci		disable_dma(host->dma);
7018c2ecf20Sopenharmony_ci		clear_dma_ff(host->dma);
7028c2ecf20Sopenharmony_ci		count = get_dma_residue(host->dma);
7038c2ecf20Sopenharmony_ci		release_dma_lock(dmaflags);
7048c2ecf20Sopenharmony_ci
7058c2ecf20Sopenharmony_ci		data->bytes_xfered = host->mrq->data->blocks *
7068c2ecf20Sopenharmony_ci			host->mrq->data->blksz - count;
7078c2ecf20Sopenharmony_ci		data->bytes_xfered -= data->bytes_xfered % data->blksz;
7088c2ecf20Sopenharmony_ci
7098c2ecf20Sopenharmony_ci		/*
7108c2ecf20Sopenharmony_ci		 * Any leftover data?
7118c2ecf20Sopenharmony_ci		 */
7128c2ecf20Sopenharmony_ci		if (count) {
7138c2ecf20Sopenharmony_ci			pr_err("%s: Incomplete DMA transfer. "
7148c2ecf20Sopenharmony_ci				"%d bytes left.\n",
7158c2ecf20Sopenharmony_ci				mmc_hostname(host->mmc), count);
7168c2ecf20Sopenharmony_ci
7178c2ecf20Sopenharmony_ci			if (!data->error)
7188c2ecf20Sopenharmony_ci				data->error = -EIO;
7198c2ecf20Sopenharmony_ci		} else {
7208c2ecf20Sopenharmony_ci			/*
7218c2ecf20Sopenharmony_ci			 * Transfer data from DMA buffer to
7228c2ecf20Sopenharmony_ci			 * SG list.
7238c2ecf20Sopenharmony_ci			 */
7248c2ecf20Sopenharmony_ci			if (data->flags & MMC_DATA_READ)
7258c2ecf20Sopenharmony_ci				wbsd_dma_to_sg(host, data);
7268c2ecf20Sopenharmony_ci		}
7278c2ecf20Sopenharmony_ci
7288c2ecf20Sopenharmony_ci		if (data->error) {
7298c2ecf20Sopenharmony_ci			if (data->bytes_xfered)
7308c2ecf20Sopenharmony_ci				data->bytes_xfered -= data->blksz;
7318c2ecf20Sopenharmony_ci		}
7328c2ecf20Sopenharmony_ci	}
7338c2ecf20Sopenharmony_ci
7348c2ecf20Sopenharmony_ci	wbsd_request_end(host, host->mrq);
7358c2ecf20Sopenharmony_ci}
7368c2ecf20Sopenharmony_ci
7378c2ecf20Sopenharmony_ci/*****************************************************************************\
7388c2ecf20Sopenharmony_ci *                                                                           *
7398c2ecf20Sopenharmony_ci * MMC layer callbacks                                                       *
7408c2ecf20Sopenharmony_ci *                                                                           *
7418c2ecf20Sopenharmony_ci\*****************************************************************************/
7428c2ecf20Sopenharmony_ci
7438c2ecf20Sopenharmony_cistatic void wbsd_request(struct mmc_host *mmc, struct mmc_request *mrq)
7448c2ecf20Sopenharmony_ci{
7458c2ecf20Sopenharmony_ci	struct wbsd_host *host = mmc_priv(mmc);
7468c2ecf20Sopenharmony_ci	struct mmc_command *cmd;
7478c2ecf20Sopenharmony_ci
7488c2ecf20Sopenharmony_ci	/*
7498c2ecf20Sopenharmony_ci	 * Disable tasklets to avoid a deadlock.
7508c2ecf20Sopenharmony_ci	 */
7518c2ecf20Sopenharmony_ci	spin_lock_bh(&host->lock);
7528c2ecf20Sopenharmony_ci
7538c2ecf20Sopenharmony_ci	BUG_ON(host->mrq != NULL);
7548c2ecf20Sopenharmony_ci
7558c2ecf20Sopenharmony_ci	cmd = mrq->cmd;
7568c2ecf20Sopenharmony_ci
7578c2ecf20Sopenharmony_ci	host->mrq = mrq;
7588c2ecf20Sopenharmony_ci
7598c2ecf20Sopenharmony_ci	/*
7608c2ecf20Sopenharmony_ci	 * Check that there is actually a card in the slot.
7618c2ecf20Sopenharmony_ci	 */
7628c2ecf20Sopenharmony_ci	if (!(host->flags & WBSD_FCARD_PRESENT)) {
7638c2ecf20Sopenharmony_ci		cmd->error = -ENOMEDIUM;
7648c2ecf20Sopenharmony_ci		goto done;
7658c2ecf20Sopenharmony_ci	}
7668c2ecf20Sopenharmony_ci
7678c2ecf20Sopenharmony_ci	if (cmd->data) {
7688c2ecf20Sopenharmony_ci		/*
7698c2ecf20Sopenharmony_ci		 * The hardware is so delightfully stupid that it has a list
7708c2ecf20Sopenharmony_ci		 * of "data" commands. If a command isn't on this list, it'll
7718c2ecf20Sopenharmony_ci		 * just go back to the idle state and won't send any data
7728c2ecf20Sopenharmony_ci		 * interrupts.
7738c2ecf20Sopenharmony_ci		 */
7748c2ecf20Sopenharmony_ci		switch (cmd->opcode) {
7758c2ecf20Sopenharmony_ci		case SD_SWITCH_VOLTAGE:
7768c2ecf20Sopenharmony_ci		case MMC_READ_SINGLE_BLOCK:
7778c2ecf20Sopenharmony_ci		case MMC_READ_MULTIPLE_BLOCK:
7788c2ecf20Sopenharmony_ci		case MMC_WRITE_DAT_UNTIL_STOP:
7798c2ecf20Sopenharmony_ci		case MMC_WRITE_BLOCK:
7808c2ecf20Sopenharmony_ci		case MMC_WRITE_MULTIPLE_BLOCK:
7818c2ecf20Sopenharmony_ci		case MMC_PROGRAM_CID:
7828c2ecf20Sopenharmony_ci		case MMC_PROGRAM_CSD:
7838c2ecf20Sopenharmony_ci		case MMC_SEND_WRITE_PROT:
7848c2ecf20Sopenharmony_ci		case MMC_LOCK_UNLOCK:
7858c2ecf20Sopenharmony_ci		case MMC_GEN_CMD:
7868c2ecf20Sopenharmony_ci			break;
7878c2ecf20Sopenharmony_ci
7888c2ecf20Sopenharmony_ci		/* ACMDs. We don't keep track of state, so we just treat them
7898c2ecf20Sopenharmony_ci		 * like any other command. */
7908c2ecf20Sopenharmony_ci		case SD_APP_SEND_SCR:
7918c2ecf20Sopenharmony_ci			break;
7928c2ecf20Sopenharmony_ci
7938c2ecf20Sopenharmony_ci		default:
7948c2ecf20Sopenharmony_ci			pr_warn("%s: Data command %d is not supported by this controller\n",
7958c2ecf20Sopenharmony_ci				mmc_hostname(host->mmc), cmd->opcode);
7968c2ecf20Sopenharmony_ci			cmd->error = -EINVAL;
7978c2ecf20Sopenharmony_ci
7988c2ecf20Sopenharmony_ci			goto done;
7998c2ecf20Sopenharmony_ci		}
8008c2ecf20Sopenharmony_ci	}
8018c2ecf20Sopenharmony_ci
8028c2ecf20Sopenharmony_ci	/*
8038c2ecf20Sopenharmony_ci	 * Does the request include data?
8048c2ecf20Sopenharmony_ci	 */
8058c2ecf20Sopenharmony_ci	if (cmd->data) {
8068c2ecf20Sopenharmony_ci		wbsd_prepare_data(host, cmd->data);
8078c2ecf20Sopenharmony_ci
8088c2ecf20Sopenharmony_ci		if (cmd->data->error)
8098c2ecf20Sopenharmony_ci			goto done;
8108c2ecf20Sopenharmony_ci	}
8118c2ecf20Sopenharmony_ci
8128c2ecf20Sopenharmony_ci	wbsd_send_command(host, cmd);
8138c2ecf20Sopenharmony_ci
8148c2ecf20Sopenharmony_ci	/*
8158c2ecf20Sopenharmony_ci	 * If this is a data transfer the request
8168c2ecf20Sopenharmony_ci	 * will be finished after the data has
8178c2ecf20Sopenharmony_ci	 * transferred.
8188c2ecf20Sopenharmony_ci	 */
8198c2ecf20Sopenharmony_ci	if (cmd->data && !cmd->error) {
8208c2ecf20Sopenharmony_ci		/*
8218c2ecf20Sopenharmony_ci		 * Dirty fix for hardware bug.
8228c2ecf20Sopenharmony_ci		 */
8238c2ecf20Sopenharmony_ci		if (host->dma == -1)
8248c2ecf20Sopenharmony_ci			tasklet_schedule(&host->fifo_tasklet);
8258c2ecf20Sopenharmony_ci
8268c2ecf20Sopenharmony_ci		spin_unlock_bh(&host->lock);
8278c2ecf20Sopenharmony_ci
8288c2ecf20Sopenharmony_ci		return;
8298c2ecf20Sopenharmony_ci	}
8308c2ecf20Sopenharmony_ci
8318c2ecf20Sopenharmony_cidone:
8328c2ecf20Sopenharmony_ci	wbsd_request_end(host, mrq);
8338c2ecf20Sopenharmony_ci
8348c2ecf20Sopenharmony_ci	spin_unlock_bh(&host->lock);
8358c2ecf20Sopenharmony_ci}
8368c2ecf20Sopenharmony_ci
8378c2ecf20Sopenharmony_cistatic void wbsd_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
8388c2ecf20Sopenharmony_ci{
8398c2ecf20Sopenharmony_ci	struct wbsd_host *host = mmc_priv(mmc);
8408c2ecf20Sopenharmony_ci	u8 clk, setup, pwr;
8418c2ecf20Sopenharmony_ci
8428c2ecf20Sopenharmony_ci	spin_lock_bh(&host->lock);
8438c2ecf20Sopenharmony_ci
8448c2ecf20Sopenharmony_ci	/*
8458c2ecf20Sopenharmony_ci	 * Reset the chip on each power off.
8468c2ecf20Sopenharmony_ci	 * Should clear out any weird states.
8478c2ecf20Sopenharmony_ci	 */
8488c2ecf20Sopenharmony_ci	if (ios->power_mode == MMC_POWER_OFF)
8498c2ecf20Sopenharmony_ci		wbsd_init_device(host);
8508c2ecf20Sopenharmony_ci
8518c2ecf20Sopenharmony_ci	if (ios->clock >= 24000000)
8528c2ecf20Sopenharmony_ci		clk = WBSD_CLK_24M;
8538c2ecf20Sopenharmony_ci	else if (ios->clock >= 16000000)
8548c2ecf20Sopenharmony_ci		clk = WBSD_CLK_16M;
8558c2ecf20Sopenharmony_ci	else if (ios->clock >= 12000000)
8568c2ecf20Sopenharmony_ci		clk = WBSD_CLK_12M;
8578c2ecf20Sopenharmony_ci	else
8588c2ecf20Sopenharmony_ci		clk = WBSD_CLK_375K;
8598c2ecf20Sopenharmony_ci
8608c2ecf20Sopenharmony_ci	/*
8618c2ecf20Sopenharmony_ci	 * Only write to the clock register when
8628c2ecf20Sopenharmony_ci	 * there is an actual change.
8638c2ecf20Sopenharmony_ci	 */
8648c2ecf20Sopenharmony_ci	if (clk != host->clk) {
8658c2ecf20Sopenharmony_ci		wbsd_write_index(host, WBSD_IDX_CLK, clk);
8668c2ecf20Sopenharmony_ci		host->clk = clk;
8678c2ecf20Sopenharmony_ci	}
8688c2ecf20Sopenharmony_ci
8698c2ecf20Sopenharmony_ci	/*
8708c2ecf20Sopenharmony_ci	 * Power up card.
8718c2ecf20Sopenharmony_ci	 */
8728c2ecf20Sopenharmony_ci	if (ios->power_mode != MMC_POWER_OFF) {
8738c2ecf20Sopenharmony_ci		pwr = inb(host->base + WBSD_CSR);
8748c2ecf20Sopenharmony_ci		pwr &= ~WBSD_POWER_N;
8758c2ecf20Sopenharmony_ci		outb(pwr, host->base + WBSD_CSR);
8768c2ecf20Sopenharmony_ci	}
8778c2ecf20Sopenharmony_ci
8788c2ecf20Sopenharmony_ci	/*
8798c2ecf20Sopenharmony_ci	 * MMC cards need to have pin 1 high during init.
8808c2ecf20Sopenharmony_ci	 * It wreaks havoc with the card detection though so
8818c2ecf20Sopenharmony_ci	 * that needs to be disabled.
8828c2ecf20Sopenharmony_ci	 */
8838c2ecf20Sopenharmony_ci	setup = wbsd_read_index(host, WBSD_IDX_SETUP);
8848c2ecf20Sopenharmony_ci	if (ios->chip_select == MMC_CS_HIGH) {
8858c2ecf20Sopenharmony_ci		BUG_ON(ios->bus_width != MMC_BUS_WIDTH_1);
8868c2ecf20Sopenharmony_ci		setup |= WBSD_DAT3_H;
8878c2ecf20Sopenharmony_ci		host->flags |= WBSD_FIGNORE_DETECT;
8888c2ecf20Sopenharmony_ci	} else {
8898c2ecf20Sopenharmony_ci		if (setup & WBSD_DAT3_H) {
8908c2ecf20Sopenharmony_ci			setup &= ~WBSD_DAT3_H;
8918c2ecf20Sopenharmony_ci
8928c2ecf20Sopenharmony_ci			/*
8938c2ecf20Sopenharmony_ci			 * We cannot resume card detection immediately
8948c2ecf20Sopenharmony_ci			 * because of capacitance and delays in the chip.
8958c2ecf20Sopenharmony_ci			 */
8968c2ecf20Sopenharmony_ci			mod_timer(&host->ignore_timer, jiffies + HZ / 100);
8978c2ecf20Sopenharmony_ci		}
8988c2ecf20Sopenharmony_ci	}
8998c2ecf20Sopenharmony_ci	wbsd_write_index(host, WBSD_IDX_SETUP, setup);
9008c2ecf20Sopenharmony_ci
9018c2ecf20Sopenharmony_ci	/*
9028c2ecf20Sopenharmony_ci	 * Store bus width for later. Will be used when
9038c2ecf20Sopenharmony_ci	 * setting up the data transfer.
9048c2ecf20Sopenharmony_ci	 */
9058c2ecf20Sopenharmony_ci	host->bus_width = ios->bus_width;
9068c2ecf20Sopenharmony_ci
9078c2ecf20Sopenharmony_ci	spin_unlock_bh(&host->lock);
9088c2ecf20Sopenharmony_ci}
9098c2ecf20Sopenharmony_ci
9108c2ecf20Sopenharmony_cistatic int wbsd_get_ro(struct mmc_host *mmc)
9118c2ecf20Sopenharmony_ci{
9128c2ecf20Sopenharmony_ci	struct wbsd_host *host = mmc_priv(mmc);
9138c2ecf20Sopenharmony_ci	u8 csr;
9148c2ecf20Sopenharmony_ci
9158c2ecf20Sopenharmony_ci	spin_lock_bh(&host->lock);
9168c2ecf20Sopenharmony_ci
9178c2ecf20Sopenharmony_ci	csr = inb(host->base + WBSD_CSR);
9188c2ecf20Sopenharmony_ci	csr |= WBSD_MSLED;
9198c2ecf20Sopenharmony_ci	outb(csr, host->base + WBSD_CSR);
9208c2ecf20Sopenharmony_ci
9218c2ecf20Sopenharmony_ci	mdelay(1);
9228c2ecf20Sopenharmony_ci
9238c2ecf20Sopenharmony_ci	csr = inb(host->base + WBSD_CSR);
9248c2ecf20Sopenharmony_ci	csr &= ~WBSD_MSLED;
9258c2ecf20Sopenharmony_ci	outb(csr, host->base + WBSD_CSR);
9268c2ecf20Sopenharmony_ci
9278c2ecf20Sopenharmony_ci	spin_unlock_bh(&host->lock);
9288c2ecf20Sopenharmony_ci
9298c2ecf20Sopenharmony_ci	return !!(csr & WBSD_WRPT);
9308c2ecf20Sopenharmony_ci}
9318c2ecf20Sopenharmony_ci
9328c2ecf20Sopenharmony_cistatic const struct mmc_host_ops wbsd_ops = {
9338c2ecf20Sopenharmony_ci	.request	= wbsd_request,
9348c2ecf20Sopenharmony_ci	.set_ios	= wbsd_set_ios,
9358c2ecf20Sopenharmony_ci	.get_ro		= wbsd_get_ro,
9368c2ecf20Sopenharmony_ci};
9378c2ecf20Sopenharmony_ci
9388c2ecf20Sopenharmony_ci/*****************************************************************************\
9398c2ecf20Sopenharmony_ci *                                                                           *
9408c2ecf20Sopenharmony_ci * Interrupt handling                                                        *
9418c2ecf20Sopenharmony_ci *                                                                           *
9428c2ecf20Sopenharmony_ci\*****************************************************************************/
9438c2ecf20Sopenharmony_ci
9448c2ecf20Sopenharmony_ci/*
9458c2ecf20Sopenharmony_ci * Helper function to reset detection ignore
9468c2ecf20Sopenharmony_ci */
9478c2ecf20Sopenharmony_ci
9488c2ecf20Sopenharmony_cistatic void wbsd_reset_ignore(struct timer_list *t)
9498c2ecf20Sopenharmony_ci{
9508c2ecf20Sopenharmony_ci	struct wbsd_host *host = from_timer(host, t, ignore_timer);
9518c2ecf20Sopenharmony_ci
9528c2ecf20Sopenharmony_ci	BUG_ON(host == NULL);
9538c2ecf20Sopenharmony_ci
9548c2ecf20Sopenharmony_ci	DBG("Resetting card detection ignore\n");
9558c2ecf20Sopenharmony_ci
9568c2ecf20Sopenharmony_ci	spin_lock_bh(&host->lock);
9578c2ecf20Sopenharmony_ci
9588c2ecf20Sopenharmony_ci	host->flags &= ~WBSD_FIGNORE_DETECT;
9598c2ecf20Sopenharmony_ci
9608c2ecf20Sopenharmony_ci	/*
9618c2ecf20Sopenharmony_ci	 * Card status might have changed during the
9628c2ecf20Sopenharmony_ci	 * blackout.
9638c2ecf20Sopenharmony_ci	 */
9648c2ecf20Sopenharmony_ci	tasklet_schedule(&host->card_tasklet);
9658c2ecf20Sopenharmony_ci
9668c2ecf20Sopenharmony_ci	spin_unlock_bh(&host->lock);
9678c2ecf20Sopenharmony_ci}
9688c2ecf20Sopenharmony_ci
9698c2ecf20Sopenharmony_ci/*
9708c2ecf20Sopenharmony_ci * Tasklets
9718c2ecf20Sopenharmony_ci */
9728c2ecf20Sopenharmony_ci
9738c2ecf20Sopenharmony_cistatic inline struct mmc_data *wbsd_get_data(struct wbsd_host *host)
9748c2ecf20Sopenharmony_ci{
9758c2ecf20Sopenharmony_ci	WARN_ON(!host->mrq);
9768c2ecf20Sopenharmony_ci	if (!host->mrq)
9778c2ecf20Sopenharmony_ci		return NULL;
9788c2ecf20Sopenharmony_ci
9798c2ecf20Sopenharmony_ci	WARN_ON(!host->mrq->cmd);
9808c2ecf20Sopenharmony_ci	if (!host->mrq->cmd)
9818c2ecf20Sopenharmony_ci		return NULL;
9828c2ecf20Sopenharmony_ci
9838c2ecf20Sopenharmony_ci	WARN_ON(!host->mrq->cmd->data);
9848c2ecf20Sopenharmony_ci	if (!host->mrq->cmd->data)
9858c2ecf20Sopenharmony_ci		return NULL;
9868c2ecf20Sopenharmony_ci
9878c2ecf20Sopenharmony_ci	return host->mrq->cmd->data;
9888c2ecf20Sopenharmony_ci}
9898c2ecf20Sopenharmony_ci
9908c2ecf20Sopenharmony_cistatic void wbsd_tasklet_card(unsigned long param)
9918c2ecf20Sopenharmony_ci{
9928c2ecf20Sopenharmony_ci	struct wbsd_host *host = (struct wbsd_host *)param;
9938c2ecf20Sopenharmony_ci	u8 csr;
9948c2ecf20Sopenharmony_ci	int delay = -1;
9958c2ecf20Sopenharmony_ci
9968c2ecf20Sopenharmony_ci	spin_lock(&host->lock);
9978c2ecf20Sopenharmony_ci
9988c2ecf20Sopenharmony_ci	if (host->flags & WBSD_FIGNORE_DETECT) {
9998c2ecf20Sopenharmony_ci		spin_unlock(&host->lock);
10008c2ecf20Sopenharmony_ci		return;
10018c2ecf20Sopenharmony_ci	}
10028c2ecf20Sopenharmony_ci
10038c2ecf20Sopenharmony_ci	csr = inb(host->base + WBSD_CSR);
10048c2ecf20Sopenharmony_ci	WARN_ON(csr == 0xff);
10058c2ecf20Sopenharmony_ci
10068c2ecf20Sopenharmony_ci	if (csr & WBSD_CARDPRESENT) {
10078c2ecf20Sopenharmony_ci		if (!(host->flags & WBSD_FCARD_PRESENT)) {
10088c2ecf20Sopenharmony_ci			DBG("Card inserted\n");
10098c2ecf20Sopenharmony_ci			host->flags |= WBSD_FCARD_PRESENT;
10108c2ecf20Sopenharmony_ci
10118c2ecf20Sopenharmony_ci			delay = 500;
10128c2ecf20Sopenharmony_ci		}
10138c2ecf20Sopenharmony_ci	} else if (host->flags & WBSD_FCARD_PRESENT) {
10148c2ecf20Sopenharmony_ci		DBG("Card removed\n");
10158c2ecf20Sopenharmony_ci		host->flags &= ~WBSD_FCARD_PRESENT;
10168c2ecf20Sopenharmony_ci
10178c2ecf20Sopenharmony_ci		if (host->mrq) {
10188c2ecf20Sopenharmony_ci			pr_err("%s: Card removed during transfer!\n",
10198c2ecf20Sopenharmony_ci				mmc_hostname(host->mmc));
10208c2ecf20Sopenharmony_ci			wbsd_reset(host);
10218c2ecf20Sopenharmony_ci
10228c2ecf20Sopenharmony_ci			host->mrq->cmd->error = -ENOMEDIUM;
10238c2ecf20Sopenharmony_ci			tasklet_schedule(&host->finish_tasklet);
10248c2ecf20Sopenharmony_ci		}
10258c2ecf20Sopenharmony_ci
10268c2ecf20Sopenharmony_ci		delay = 0;
10278c2ecf20Sopenharmony_ci	}
10288c2ecf20Sopenharmony_ci
10298c2ecf20Sopenharmony_ci	/*
10308c2ecf20Sopenharmony_ci	 * Unlock first since we might get a call back.
10318c2ecf20Sopenharmony_ci	 */
10328c2ecf20Sopenharmony_ci
10338c2ecf20Sopenharmony_ci	spin_unlock(&host->lock);
10348c2ecf20Sopenharmony_ci
10358c2ecf20Sopenharmony_ci	if (delay != -1)
10368c2ecf20Sopenharmony_ci		mmc_detect_change(host->mmc, msecs_to_jiffies(delay));
10378c2ecf20Sopenharmony_ci}
10388c2ecf20Sopenharmony_ci
10398c2ecf20Sopenharmony_cistatic void wbsd_tasklet_fifo(unsigned long param)
10408c2ecf20Sopenharmony_ci{
10418c2ecf20Sopenharmony_ci	struct wbsd_host *host = (struct wbsd_host *)param;
10428c2ecf20Sopenharmony_ci	struct mmc_data *data;
10438c2ecf20Sopenharmony_ci
10448c2ecf20Sopenharmony_ci	spin_lock(&host->lock);
10458c2ecf20Sopenharmony_ci
10468c2ecf20Sopenharmony_ci	if (!host->mrq)
10478c2ecf20Sopenharmony_ci		goto end;
10488c2ecf20Sopenharmony_ci
10498c2ecf20Sopenharmony_ci	data = wbsd_get_data(host);
10508c2ecf20Sopenharmony_ci	if (!data)
10518c2ecf20Sopenharmony_ci		goto end;
10528c2ecf20Sopenharmony_ci
10538c2ecf20Sopenharmony_ci	if (data->flags & MMC_DATA_WRITE)
10548c2ecf20Sopenharmony_ci		wbsd_fill_fifo(host);
10558c2ecf20Sopenharmony_ci	else
10568c2ecf20Sopenharmony_ci		wbsd_empty_fifo(host);
10578c2ecf20Sopenharmony_ci
10588c2ecf20Sopenharmony_ci	/*
10598c2ecf20Sopenharmony_ci	 * Done?
10608c2ecf20Sopenharmony_ci	 */
10618c2ecf20Sopenharmony_ci	if (host->num_sg == 0) {
10628c2ecf20Sopenharmony_ci		wbsd_write_index(host, WBSD_IDX_FIFOEN, 0);
10638c2ecf20Sopenharmony_ci		tasklet_schedule(&host->finish_tasklet);
10648c2ecf20Sopenharmony_ci	}
10658c2ecf20Sopenharmony_ci
10668c2ecf20Sopenharmony_ciend:
10678c2ecf20Sopenharmony_ci	spin_unlock(&host->lock);
10688c2ecf20Sopenharmony_ci}
10698c2ecf20Sopenharmony_ci
10708c2ecf20Sopenharmony_cistatic void wbsd_tasklet_crc(unsigned long param)
10718c2ecf20Sopenharmony_ci{
10728c2ecf20Sopenharmony_ci	struct wbsd_host *host = (struct wbsd_host *)param;
10738c2ecf20Sopenharmony_ci	struct mmc_data *data;
10748c2ecf20Sopenharmony_ci
10758c2ecf20Sopenharmony_ci	spin_lock(&host->lock);
10768c2ecf20Sopenharmony_ci
10778c2ecf20Sopenharmony_ci	if (!host->mrq)
10788c2ecf20Sopenharmony_ci		goto end;
10798c2ecf20Sopenharmony_ci
10808c2ecf20Sopenharmony_ci	data = wbsd_get_data(host);
10818c2ecf20Sopenharmony_ci	if (!data)
10828c2ecf20Sopenharmony_ci		goto end;
10838c2ecf20Sopenharmony_ci
10848c2ecf20Sopenharmony_ci	DBGF("CRC error\n");
10858c2ecf20Sopenharmony_ci
10868c2ecf20Sopenharmony_ci	data->error = -EILSEQ;
10878c2ecf20Sopenharmony_ci
10888c2ecf20Sopenharmony_ci	tasklet_schedule(&host->finish_tasklet);
10898c2ecf20Sopenharmony_ci
10908c2ecf20Sopenharmony_ciend:
10918c2ecf20Sopenharmony_ci	spin_unlock(&host->lock);
10928c2ecf20Sopenharmony_ci}
10938c2ecf20Sopenharmony_ci
10948c2ecf20Sopenharmony_cistatic void wbsd_tasklet_timeout(unsigned long param)
10958c2ecf20Sopenharmony_ci{
10968c2ecf20Sopenharmony_ci	struct wbsd_host *host = (struct wbsd_host *)param;
10978c2ecf20Sopenharmony_ci	struct mmc_data *data;
10988c2ecf20Sopenharmony_ci
10998c2ecf20Sopenharmony_ci	spin_lock(&host->lock);
11008c2ecf20Sopenharmony_ci
11018c2ecf20Sopenharmony_ci	if (!host->mrq)
11028c2ecf20Sopenharmony_ci		goto end;
11038c2ecf20Sopenharmony_ci
11048c2ecf20Sopenharmony_ci	data = wbsd_get_data(host);
11058c2ecf20Sopenharmony_ci	if (!data)
11068c2ecf20Sopenharmony_ci		goto end;
11078c2ecf20Sopenharmony_ci
11088c2ecf20Sopenharmony_ci	DBGF("Timeout\n");
11098c2ecf20Sopenharmony_ci
11108c2ecf20Sopenharmony_ci	data->error = -ETIMEDOUT;
11118c2ecf20Sopenharmony_ci
11128c2ecf20Sopenharmony_ci	tasklet_schedule(&host->finish_tasklet);
11138c2ecf20Sopenharmony_ci
11148c2ecf20Sopenharmony_ciend:
11158c2ecf20Sopenharmony_ci	spin_unlock(&host->lock);
11168c2ecf20Sopenharmony_ci}
11178c2ecf20Sopenharmony_ci
11188c2ecf20Sopenharmony_cistatic void wbsd_tasklet_finish(unsigned long param)
11198c2ecf20Sopenharmony_ci{
11208c2ecf20Sopenharmony_ci	struct wbsd_host *host = (struct wbsd_host *)param;
11218c2ecf20Sopenharmony_ci	struct mmc_data *data;
11228c2ecf20Sopenharmony_ci
11238c2ecf20Sopenharmony_ci	spin_lock(&host->lock);
11248c2ecf20Sopenharmony_ci
11258c2ecf20Sopenharmony_ci	WARN_ON(!host->mrq);
11268c2ecf20Sopenharmony_ci	if (!host->mrq)
11278c2ecf20Sopenharmony_ci		goto end;
11288c2ecf20Sopenharmony_ci
11298c2ecf20Sopenharmony_ci	data = wbsd_get_data(host);
11308c2ecf20Sopenharmony_ci	if (!data)
11318c2ecf20Sopenharmony_ci		goto end;
11328c2ecf20Sopenharmony_ci
11338c2ecf20Sopenharmony_ci	wbsd_finish_data(host, data);
11348c2ecf20Sopenharmony_ci
11358c2ecf20Sopenharmony_ciend:
11368c2ecf20Sopenharmony_ci	spin_unlock(&host->lock);
11378c2ecf20Sopenharmony_ci}
11388c2ecf20Sopenharmony_ci
11398c2ecf20Sopenharmony_ci/*
11408c2ecf20Sopenharmony_ci * Interrupt handling
11418c2ecf20Sopenharmony_ci */
11428c2ecf20Sopenharmony_ci
11438c2ecf20Sopenharmony_cistatic irqreturn_t wbsd_irq(int irq, void *dev_id)
11448c2ecf20Sopenharmony_ci{
11458c2ecf20Sopenharmony_ci	struct wbsd_host *host = dev_id;
11468c2ecf20Sopenharmony_ci	int isr;
11478c2ecf20Sopenharmony_ci
11488c2ecf20Sopenharmony_ci	isr = inb(host->base + WBSD_ISR);
11498c2ecf20Sopenharmony_ci
11508c2ecf20Sopenharmony_ci	/*
11518c2ecf20Sopenharmony_ci	 * Was it actually our hardware that caused the interrupt?
11528c2ecf20Sopenharmony_ci	 */
11538c2ecf20Sopenharmony_ci	if (isr == 0xff || isr == 0x00)
11548c2ecf20Sopenharmony_ci		return IRQ_NONE;
11558c2ecf20Sopenharmony_ci
11568c2ecf20Sopenharmony_ci	host->isr |= isr;
11578c2ecf20Sopenharmony_ci
11588c2ecf20Sopenharmony_ci	/*
11598c2ecf20Sopenharmony_ci	 * Schedule tasklets as needed.
11608c2ecf20Sopenharmony_ci	 */
11618c2ecf20Sopenharmony_ci	if (isr & WBSD_INT_CARD)
11628c2ecf20Sopenharmony_ci		tasklet_schedule(&host->card_tasklet);
11638c2ecf20Sopenharmony_ci	if (isr & WBSD_INT_FIFO_THRE)
11648c2ecf20Sopenharmony_ci		tasklet_schedule(&host->fifo_tasklet);
11658c2ecf20Sopenharmony_ci	if (isr & WBSD_INT_CRC)
11668c2ecf20Sopenharmony_ci		tasklet_hi_schedule(&host->crc_tasklet);
11678c2ecf20Sopenharmony_ci	if (isr & WBSD_INT_TIMEOUT)
11688c2ecf20Sopenharmony_ci		tasklet_hi_schedule(&host->timeout_tasklet);
11698c2ecf20Sopenharmony_ci	if (isr & WBSD_INT_TC)
11708c2ecf20Sopenharmony_ci		tasklet_schedule(&host->finish_tasklet);
11718c2ecf20Sopenharmony_ci
11728c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
11738c2ecf20Sopenharmony_ci}
11748c2ecf20Sopenharmony_ci
11758c2ecf20Sopenharmony_ci/*****************************************************************************\
11768c2ecf20Sopenharmony_ci *                                                                           *
11778c2ecf20Sopenharmony_ci * Device initialisation and shutdown                                        *
11788c2ecf20Sopenharmony_ci *                                                                           *
11798c2ecf20Sopenharmony_ci\*****************************************************************************/
11808c2ecf20Sopenharmony_ci
11818c2ecf20Sopenharmony_ci/*
11828c2ecf20Sopenharmony_ci * Allocate/free MMC structure.
11838c2ecf20Sopenharmony_ci */
11848c2ecf20Sopenharmony_ci
11858c2ecf20Sopenharmony_cistatic int wbsd_alloc_mmc(struct device *dev)
11868c2ecf20Sopenharmony_ci{
11878c2ecf20Sopenharmony_ci	struct mmc_host *mmc;
11888c2ecf20Sopenharmony_ci	struct wbsd_host *host;
11898c2ecf20Sopenharmony_ci
11908c2ecf20Sopenharmony_ci	/*
11918c2ecf20Sopenharmony_ci	 * Allocate MMC structure.
11928c2ecf20Sopenharmony_ci	 */
11938c2ecf20Sopenharmony_ci	mmc = mmc_alloc_host(sizeof(struct wbsd_host), dev);
11948c2ecf20Sopenharmony_ci	if (!mmc)
11958c2ecf20Sopenharmony_ci		return -ENOMEM;
11968c2ecf20Sopenharmony_ci
11978c2ecf20Sopenharmony_ci	host = mmc_priv(mmc);
11988c2ecf20Sopenharmony_ci	host->mmc = mmc;
11998c2ecf20Sopenharmony_ci
12008c2ecf20Sopenharmony_ci	host->dma = -1;
12018c2ecf20Sopenharmony_ci
12028c2ecf20Sopenharmony_ci	/*
12038c2ecf20Sopenharmony_ci	 * Set host parameters.
12048c2ecf20Sopenharmony_ci	 */
12058c2ecf20Sopenharmony_ci	mmc->ops = &wbsd_ops;
12068c2ecf20Sopenharmony_ci	mmc->f_min = 375000;
12078c2ecf20Sopenharmony_ci	mmc->f_max = 24000000;
12088c2ecf20Sopenharmony_ci	mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
12098c2ecf20Sopenharmony_ci	mmc->caps = MMC_CAP_4_BIT_DATA;
12108c2ecf20Sopenharmony_ci
12118c2ecf20Sopenharmony_ci	spin_lock_init(&host->lock);
12128c2ecf20Sopenharmony_ci
12138c2ecf20Sopenharmony_ci	/*
12148c2ecf20Sopenharmony_ci	 * Set up timers
12158c2ecf20Sopenharmony_ci	 */
12168c2ecf20Sopenharmony_ci	timer_setup(&host->ignore_timer, wbsd_reset_ignore, 0);
12178c2ecf20Sopenharmony_ci
12188c2ecf20Sopenharmony_ci	/*
12198c2ecf20Sopenharmony_ci	 * Maximum number of segments. Worst case is one sector per segment
12208c2ecf20Sopenharmony_ci	 * so this will be 64kB/512.
12218c2ecf20Sopenharmony_ci	 */
12228c2ecf20Sopenharmony_ci	mmc->max_segs = 128;
12238c2ecf20Sopenharmony_ci
12248c2ecf20Sopenharmony_ci	/*
12258c2ecf20Sopenharmony_ci	 * Maximum request size. Also limited by 64KiB buffer.
12268c2ecf20Sopenharmony_ci	 */
12278c2ecf20Sopenharmony_ci	mmc->max_req_size = 65536;
12288c2ecf20Sopenharmony_ci
12298c2ecf20Sopenharmony_ci	/*
12308c2ecf20Sopenharmony_ci	 * Maximum segment size. Could be one segment with the maximum number
12318c2ecf20Sopenharmony_ci	 * of bytes.
12328c2ecf20Sopenharmony_ci	 */
12338c2ecf20Sopenharmony_ci	mmc->max_seg_size = mmc->max_req_size;
12348c2ecf20Sopenharmony_ci
12358c2ecf20Sopenharmony_ci	/*
12368c2ecf20Sopenharmony_ci	 * Maximum block size. We have 12 bits (= 4095) but have to subtract
12378c2ecf20Sopenharmony_ci	 * space for CRC. So the maximum is 4095 - 4*2 = 4087.
12388c2ecf20Sopenharmony_ci	 */
12398c2ecf20Sopenharmony_ci	mmc->max_blk_size = 4087;
12408c2ecf20Sopenharmony_ci
12418c2ecf20Sopenharmony_ci	/*
12428c2ecf20Sopenharmony_ci	 * Maximum block count. There is no real limit so the maximum
12438c2ecf20Sopenharmony_ci	 * request size will be the only restriction.
12448c2ecf20Sopenharmony_ci	 */
12458c2ecf20Sopenharmony_ci	mmc->max_blk_count = mmc->max_req_size;
12468c2ecf20Sopenharmony_ci
12478c2ecf20Sopenharmony_ci	dev_set_drvdata(dev, mmc);
12488c2ecf20Sopenharmony_ci
12498c2ecf20Sopenharmony_ci	return 0;
12508c2ecf20Sopenharmony_ci}
12518c2ecf20Sopenharmony_ci
12528c2ecf20Sopenharmony_cistatic void wbsd_free_mmc(struct device *dev)
12538c2ecf20Sopenharmony_ci{
12548c2ecf20Sopenharmony_ci	struct mmc_host *mmc;
12558c2ecf20Sopenharmony_ci	struct wbsd_host *host;
12568c2ecf20Sopenharmony_ci
12578c2ecf20Sopenharmony_ci	mmc = dev_get_drvdata(dev);
12588c2ecf20Sopenharmony_ci	if (!mmc)
12598c2ecf20Sopenharmony_ci		return;
12608c2ecf20Sopenharmony_ci
12618c2ecf20Sopenharmony_ci	host = mmc_priv(mmc);
12628c2ecf20Sopenharmony_ci	BUG_ON(host == NULL);
12638c2ecf20Sopenharmony_ci
12648c2ecf20Sopenharmony_ci	del_timer_sync(&host->ignore_timer);
12658c2ecf20Sopenharmony_ci
12668c2ecf20Sopenharmony_ci	mmc_free_host(mmc);
12678c2ecf20Sopenharmony_ci
12688c2ecf20Sopenharmony_ci	dev_set_drvdata(dev, NULL);
12698c2ecf20Sopenharmony_ci}
12708c2ecf20Sopenharmony_ci
12718c2ecf20Sopenharmony_ci/*
12728c2ecf20Sopenharmony_ci * Scan for known chip id:s
12738c2ecf20Sopenharmony_ci */
12748c2ecf20Sopenharmony_ci
12758c2ecf20Sopenharmony_cistatic int wbsd_scan(struct wbsd_host *host)
12768c2ecf20Sopenharmony_ci{
12778c2ecf20Sopenharmony_ci	int i, j, k;
12788c2ecf20Sopenharmony_ci	int id;
12798c2ecf20Sopenharmony_ci
12808c2ecf20Sopenharmony_ci	/*
12818c2ecf20Sopenharmony_ci	 * Iterate through all ports, all codes to
12828c2ecf20Sopenharmony_ci	 * find hardware that is in our known list.
12838c2ecf20Sopenharmony_ci	 */
12848c2ecf20Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(config_ports); i++) {
12858c2ecf20Sopenharmony_ci		if (!request_region(config_ports[i], 2, DRIVER_NAME))
12868c2ecf20Sopenharmony_ci			continue;
12878c2ecf20Sopenharmony_ci
12888c2ecf20Sopenharmony_ci		for (j = 0; j < ARRAY_SIZE(unlock_codes); j++) {
12898c2ecf20Sopenharmony_ci			id = 0xFFFF;
12908c2ecf20Sopenharmony_ci
12918c2ecf20Sopenharmony_ci			host->config = config_ports[i];
12928c2ecf20Sopenharmony_ci			host->unlock_code = unlock_codes[j];
12938c2ecf20Sopenharmony_ci
12948c2ecf20Sopenharmony_ci			wbsd_unlock_config(host);
12958c2ecf20Sopenharmony_ci
12968c2ecf20Sopenharmony_ci			outb(WBSD_CONF_ID_HI, config_ports[i]);
12978c2ecf20Sopenharmony_ci			id = inb(config_ports[i] + 1) << 8;
12988c2ecf20Sopenharmony_ci
12998c2ecf20Sopenharmony_ci			outb(WBSD_CONF_ID_LO, config_ports[i]);
13008c2ecf20Sopenharmony_ci			id |= inb(config_ports[i] + 1);
13018c2ecf20Sopenharmony_ci
13028c2ecf20Sopenharmony_ci			wbsd_lock_config(host);
13038c2ecf20Sopenharmony_ci
13048c2ecf20Sopenharmony_ci			for (k = 0; k < ARRAY_SIZE(valid_ids); k++) {
13058c2ecf20Sopenharmony_ci				if (id == valid_ids[k]) {
13068c2ecf20Sopenharmony_ci					host->chip_id = id;
13078c2ecf20Sopenharmony_ci
13088c2ecf20Sopenharmony_ci					return 0;
13098c2ecf20Sopenharmony_ci				}
13108c2ecf20Sopenharmony_ci			}
13118c2ecf20Sopenharmony_ci
13128c2ecf20Sopenharmony_ci			if (id != 0xFFFF) {
13138c2ecf20Sopenharmony_ci				DBG("Unknown hardware (id %x) found at %x\n",
13148c2ecf20Sopenharmony_ci					id, config_ports[i]);
13158c2ecf20Sopenharmony_ci			}
13168c2ecf20Sopenharmony_ci		}
13178c2ecf20Sopenharmony_ci
13188c2ecf20Sopenharmony_ci		release_region(config_ports[i], 2);
13198c2ecf20Sopenharmony_ci	}
13208c2ecf20Sopenharmony_ci
13218c2ecf20Sopenharmony_ci	host->config = 0;
13228c2ecf20Sopenharmony_ci	host->unlock_code = 0;
13238c2ecf20Sopenharmony_ci
13248c2ecf20Sopenharmony_ci	return -ENODEV;
13258c2ecf20Sopenharmony_ci}
13268c2ecf20Sopenharmony_ci
13278c2ecf20Sopenharmony_ci/*
13288c2ecf20Sopenharmony_ci * Allocate/free io port ranges
13298c2ecf20Sopenharmony_ci */
13308c2ecf20Sopenharmony_ci
13318c2ecf20Sopenharmony_cistatic int wbsd_request_region(struct wbsd_host *host, int base)
13328c2ecf20Sopenharmony_ci{
13338c2ecf20Sopenharmony_ci	if (base & 0x7)
13348c2ecf20Sopenharmony_ci		return -EINVAL;
13358c2ecf20Sopenharmony_ci
13368c2ecf20Sopenharmony_ci	if (!request_region(base, 8, DRIVER_NAME))
13378c2ecf20Sopenharmony_ci		return -EIO;
13388c2ecf20Sopenharmony_ci
13398c2ecf20Sopenharmony_ci	host->base = base;
13408c2ecf20Sopenharmony_ci
13418c2ecf20Sopenharmony_ci	return 0;
13428c2ecf20Sopenharmony_ci}
13438c2ecf20Sopenharmony_ci
13448c2ecf20Sopenharmony_cistatic void wbsd_release_regions(struct wbsd_host *host)
13458c2ecf20Sopenharmony_ci{
13468c2ecf20Sopenharmony_ci	if (host->base)
13478c2ecf20Sopenharmony_ci		release_region(host->base, 8);
13488c2ecf20Sopenharmony_ci
13498c2ecf20Sopenharmony_ci	host->base = 0;
13508c2ecf20Sopenharmony_ci
13518c2ecf20Sopenharmony_ci	if (host->config)
13528c2ecf20Sopenharmony_ci		release_region(host->config, 2);
13538c2ecf20Sopenharmony_ci
13548c2ecf20Sopenharmony_ci	host->config = 0;
13558c2ecf20Sopenharmony_ci}
13568c2ecf20Sopenharmony_ci
13578c2ecf20Sopenharmony_ci/*
13588c2ecf20Sopenharmony_ci * Allocate/free DMA port and buffer
13598c2ecf20Sopenharmony_ci */
13608c2ecf20Sopenharmony_ci
13618c2ecf20Sopenharmony_cistatic void wbsd_request_dma(struct wbsd_host *host, int dma)
13628c2ecf20Sopenharmony_ci{
13638c2ecf20Sopenharmony_ci	if (dma < 0)
13648c2ecf20Sopenharmony_ci		return;
13658c2ecf20Sopenharmony_ci
13668c2ecf20Sopenharmony_ci	if (request_dma(dma, DRIVER_NAME))
13678c2ecf20Sopenharmony_ci		goto err;
13688c2ecf20Sopenharmony_ci
13698c2ecf20Sopenharmony_ci	/*
13708c2ecf20Sopenharmony_ci	 * We need to allocate a special buffer in
13718c2ecf20Sopenharmony_ci	 * order for ISA to be able to DMA to it.
13728c2ecf20Sopenharmony_ci	 */
13738c2ecf20Sopenharmony_ci	host->dma_buffer = kmalloc(WBSD_DMA_SIZE,
13748c2ecf20Sopenharmony_ci		GFP_NOIO | GFP_DMA | __GFP_RETRY_MAYFAIL | __GFP_NOWARN);
13758c2ecf20Sopenharmony_ci	if (!host->dma_buffer)
13768c2ecf20Sopenharmony_ci		goto free;
13778c2ecf20Sopenharmony_ci
13788c2ecf20Sopenharmony_ci	/*
13798c2ecf20Sopenharmony_ci	 * Translate the address to a physical address.
13808c2ecf20Sopenharmony_ci	 */
13818c2ecf20Sopenharmony_ci	host->dma_addr = dma_map_single(mmc_dev(host->mmc), host->dma_buffer,
13828c2ecf20Sopenharmony_ci		WBSD_DMA_SIZE, DMA_BIDIRECTIONAL);
13838c2ecf20Sopenharmony_ci	if (dma_mapping_error(mmc_dev(host->mmc), host->dma_addr))
13848c2ecf20Sopenharmony_ci		goto kfree;
13858c2ecf20Sopenharmony_ci
13868c2ecf20Sopenharmony_ci	/*
13878c2ecf20Sopenharmony_ci	 * ISA DMA must be aligned on a 64k basis.
13888c2ecf20Sopenharmony_ci	 */
13898c2ecf20Sopenharmony_ci	if ((host->dma_addr & 0xffff) != 0)
13908c2ecf20Sopenharmony_ci		goto unmap;
13918c2ecf20Sopenharmony_ci	/*
13928c2ecf20Sopenharmony_ci	 * ISA cannot access memory above 16 MB.
13938c2ecf20Sopenharmony_ci	 */
13948c2ecf20Sopenharmony_ci	else if (host->dma_addr >= 0x1000000)
13958c2ecf20Sopenharmony_ci		goto unmap;
13968c2ecf20Sopenharmony_ci
13978c2ecf20Sopenharmony_ci	host->dma = dma;
13988c2ecf20Sopenharmony_ci
13998c2ecf20Sopenharmony_ci	return;
14008c2ecf20Sopenharmony_ci
14018c2ecf20Sopenharmony_ciunmap:
14028c2ecf20Sopenharmony_ci	/*
14038c2ecf20Sopenharmony_ci	 * If we've gotten here then there is some kind of alignment bug
14048c2ecf20Sopenharmony_ci	 */
14058c2ecf20Sopenharmony_ci	BUG_ON(1);
14068c2ecf20Sopenharmony_ci
14078c2ecf20Sopenharmony_ci	dma_unmap_single(mmc_dev(host->mmc), host->dma_addr,
14088c2ecf20Sopenharmony_ci		WBSD_DMA_SIZE, DMA_BIDIRECTIONAL);
14098c2ecf20Sopenharmony_ci	host->dma_addr = 0;
14108c2ecf20Sopenharmony_ci
14118c2ecf20Sopenharmony_cikfree:
14128c2ecf20Sopenharmony_ci	kfree(host->dma_buffer);
14138c2ecf20Sopenharmony_ci	host->dma_buffer = NULL;
14148c2ecf20Sopenharmony_ci
14158c2ecf20Sopenharmony_cifree:
14168c2ecf20Sopenharmony_ci	free_dma(dma);
14178c2ecf20Sopenharmony_ci
14188c2ecf20Sopenharmony_cierr:
14198c2ecf20Sopenharmony_ci	pr_warn(DRIVER_NAME ": Unable to allocate DMA %d - falling back on FIFO\n",
14208c2ecf20Sopenharmony_ci		dma);
14218c2ecf20Sopenharmony_ci}
14228c2ecf20Sopenharmony_ci
14238c2ecf20Sopenharmony_cistatic void wbsd_release_dma(struct wbsd_host *host)
14248c2ecf20Sopenharmony_ci{
14258c2ecf20Sopenharmony_ci	/*
14268c2ecf20Sopenharmony_ci	 * host->dma_addr is valid here iff host->dma_buffer is not NULL.
14278c2ecf20Sopenharmony_ci	 */
14288c2ecf20Sopenharmony_ci	if (host->dma_buffer) {
14298c2ecf20Sopenharmony_ci		dma_unmap_single(mmc_dev(host->mmc), host->dma_addr,
14308c2ecf20Sopenharmony_ci			WBSD_DMA_SIZE, DMA_BIDIRECTIONAL);
14318c2ecf20Sopenharmony_ci		kfree(host->dma_buffer);
14328c2ecf20Sopenharmony_ci	}
14338c2ecf20Sopenharmony_ci	if (host->dma >= 0)
14348c2ecf20Sopenharmony_ci		free_dma(host->dma);
14358c2ecf20Sopenharmony_ci
14368c2ecf20Sopenharmony_ci	host->dma = -1;
14378c2ecf20Sopenharmony_ci	host->dma_buffer = NULL;
14388c2ecf20Sopenharmony_ci	host->dma_addr = 0;
14398c2ecf20Sopenharmony_ci}
14408c2ecf20Sopenharmony_ci
14418c2ecf20Sopenharmony_ci/*
14428c2ecf20Sopenharmony_ci * Allocate/free IRQ.
14438c2ecf20Sopenharmony_ci */
14448c2ecf20Sopenharmony_ci
14458c2ecf20Sopenharmony_cistatic int wbsd_request_irq(struct wbsd_host *host, int irq)
14468c2ecf20Sopenharmony_ci{
14478c2ecf20Sopenharmony_ci	int ret;
14488c2ecf20Sopenharmony_ci
14498c2ecf20Sopenharmony_ci	/*
14508c2ecf20Sopenharmony_ci	 * Set up tasklets. Must be done before requesting interrupt.
14518c2ecf20Sopenharmony_ci	 */
14528c2ecf20Sopenharmony_ci	tasklet_init(&host->card_tasklet, wbsd_tasklet_card,
14538c2ecf20Sopenharmony_ci			(unsigned long)host);
14548c2ecf20Sopenharmony_ci	tasklet_init(&host->fifo_tasklet, wbsd_tasklet_fifo,
14558c2ecf20Sopenharmony_ci			(unsigned long)host);
14568c2ecf20Sopenharmony_ci	tasklet_init(&host->crc_tasklet, wbsd_tasklet_crc,
14578c2ecf20Sopenharmony_ci			(unsigned long)host);
14588c2ecf20Sopenharmony_ci	tasklet_init(&host->timeout_tasklet, wbsd_tasklet_timeout,
14598c2ecf20Sopenharmony_ci			(unsigned long)host);
14608c2ecf20Sopenharmony_ci	tasklet_init(&host->finish_tasklet, wbsd_tasklet_finish,
14618c2ecf20Sopenharmony_ci			(unsigned long)host);
14628c2ecf20Sopenharmony_ci
14638c2ecf20Sopenharmony_ci	/*
14648c2ecf20Sopenharmony_ci	 * Allocate interrupt.
14658c2ecf20Sopenharmony_ci	 */
14668c2ecf20Sopenharmony_ci	ret = request_irq(irq, wbsd_irq, IRQF_SHARED, DRIVER_NAME, host);
14678c2ecf20Sopenharmony_ci	if (ret)
14688c2ecf20Sopenharmony_ci		return ret;
14698c2ecf20Sopenharmony_ci
14708c2ecf20Sopenharmony_ci	host->irq = irq;
14718c2ecf20Sopenharmony_ci
14728c2ecf20Sopenharmony_ci	return 0;
14738c2ecf20Sopenharmony_ci}
14748c2ecf20Sopenharmony_ci
14758c2ecf20Sopenharmony_cistatic void  wbsd_release_irq(struct wbsd_host *host)
14768c2ecf20Sopenharmony_ci{
14778c2ecf20Sopenharmony_ci	if (!host->irq)
14788c2ecf20Sopenharmony_ci		return;
14798c2ecf20Sopenharmony_ci
14808c2ecf20Sopenharmony_ci	free_irq(host->irq, host);
14818c2ecf20Sopenharmony_ci
14828c2ecf20Sopenharmony_ci	host->irq = 0;
14838c2ecf20Sopenharmony_ci
14848c2ecf20Sopenharmony_ci	tasklet_kill(&host->card_tasklet);
14858c2ecf20Sopenharmony_ci	tasklet_kill(&host->fifo_tasklet);
14868c2ecf20Sopenharmony_ci	tasklet_kill(&host->crc_tasklet);
14878c2ecf20Sopenharmony_ci	tasklet_kill(&host->timeout_tasklet);
14888c2ecf20Sopenharmony_ci	tasklet_kill(&host->finish_tasklet);
14898c2ecf20Sopenharmony_ci}
14908c2ecf20Sopenharmony_ci
14918c2ecf20Sopenharmony_ci/*
14928c2ecf20Sopenharmony_ci * Allocate all resources for the host.
14938c2ecf20Sopenharmony_ci */
14948c2ecf20Sopenharmony_ci
14958c2ecf20Sopenharmony_cistatic int wbsd_request_resources(struct wbsd_host *host,
14968c2ecf20Sopenharmony_ci	int base, int irq, int dma)
14978c2ecf20Sopenharmony_ci{
14988c2ecf20Sopenharmony_ci	int ret;
14998c2ecf20Sopenharmony_ci
15008c2ecf20Sopenharmony_ci	/*
15018c2ecf20Sopenharmony_ci	 * Allocate I/O ports.
15028c2ecf20Sopenharmony_ci	 */
15038c2ecf20Sopenharmony_ci	ret = wbsd_request_region(host, base);
15048c2ecf20Sopenharmony_ci	if (ret)
15058c2ecf20Sopenharmony_ci		return ret;
15068c2ecf20Sopenharmony_ci
15078c2ecf20Sopenharmony_ci	/*
15088c2ecf20Sopenharmony_ci	 * Allocate interrupt.
15098c2ecf20Sopenharmony_ci	 */
15108c2ecf20Sopenharmony_ci	ret = wbsd_request_irq(host, irq);
15118c2ecf20Sopenharmony_ci	if (ret)
15128c2ecf20Sopenharmony_ci		return ret;
15138c2ecf20Sopenharmony_ci
15148c2ecf20Sopenharmony_ci	/*
15158c2ecf20Sopenharmony_ci	 * Allocate DMA.
15168c2ecf20Sopenharmony_ci	 */
15178c2ecf20Sopenharmony_ci	wbsd_request_dma(host, dma);
15188c2ecf20Sopenharmony_ci
15198c2ecf20Sopenharmony_ci	return 0;
15208c2ecf20Sopenharmony_ci}
15218c2ecf20Sopenharmony_ci
15228c2ecf20Sopenharmony_ci/*
15238c2ecf20Sopenharmony_ci * Release all resources for the host.
15248c2ecf20Sopenharmony_ci */
15258c2ecf20Sopenharmony_ci
15268c2ecf20Sopenharmony_cistatic void wbsd_release_resources(struct wbsd_host *host)
15278c2ecf20Sopenharmony_ci{
15288c2ecf20Sopenharmony_ci	wbsd_release_dma(host);
15298c2ecf20Sopenharmony_ci	wbsd_release_irq(host);
15308c2ecf20Sopenharmony_ci	wbsd_release_regions(host);
15318c2ecf20Sopenharmony_ci}
15328c2ecf20Sopenharmony_ci
15338c2ecf20Sopenharmony_ci/*
15348c2ecf20Sopenharmony_ci * Configure the resources the chip should use.
15358c2ecf20Sopenharmony_ci */
15368c2ecf20Sopenharmony_ci
15378c2ecf20Sopenharmony_cistatic void wbsd_chip_config(struct wbsd_host *host)
15388c2ecf20Sopenharmony_ci{
15398c2ecf20Sopenharmony_ci	wbsd_unlock_config(host);
15408c2ecf20Sopenharmony_ci
15418c2ecf20Sopenharmony_ci	/*
15428c2ecf20Sopenharmony_ci	 * Reset the chip.
15438c2ecf20Sopenharmony_ci	 */
15448c2ecf20Sopenharmony_ci	wbsd_write_config(host, WBSD_CONF_SWRST, 1);
15458c2ecf20Sopenharmony_ci	wbsd_write_config(host, WBSD_CONF_SWRST, 0);
15468c2ecf20Sopenharmony_ci
15478c2ecf20Sopenharmony_ci	/*
15488c2ecf20Sopenharmony_ci	 * Select SD/MMC function.
15498c2ecf20Sopenharmony_ci	 */
15508c2ecf20Sopenharmony_ci	wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD);
15518c2ecf20Sopenharmony_ci
15528c2ecf20Sopenharmony_ci	/*
15538c2ecf20Sopenharmony_ci	 * Set up card detection.
15548c2ecf20Sopenharmony_ci	 */
15558c2ecf20Sopenharmony_ci	wbsd_write_config(host, WBSD_CONF_PINS, WBSD_PINS_DETECT_GP11);
15568c2ecf20Sopenharmony_ci
15578c2ecf20Sopenharmony_ci	/*
15588c2ecf20Sopenharmony_ci	 * Configure chip
15598c2ecf20Sopenharmony_ci	 */
15608c2ecf20Sopenharmony_ci	wbsd_write_config(host, WBSD_CONF_PORT_HI, host->base >> 8);
15618c2ecf20Sopenharmony_ci	wbsd_write_config(host, WBSD_CONF_PORT_LO, host->base & 0xff);
15628c2ecf20Sopenharmony_ci
15638c2ecf20Sopenharmony_ci	wbsd_write_config(host, WBSD_CONF_IRQ, host->irq);
15648c2ecf20Sopenharmony_ci
15658c2ecf20Sopenharmony_ci	if (host->dma >= 0)
15668c2ecf20Sopenharmony_ci		wbsd_write_config(host, WBSD_CONF_DRQ, host->dma);
15678c2ecf20Sopenharmony_ci
15688c2ecf20Sopenharmony_ci	/*
15698c2ecf20Sopenharmony_ci	 * Enable and power up chip.
15708c2ecf20Sopenharmony_ci	 */
15718c2ecf20Sopenharmony_ci	wbsd_write_config(host, WBSD_CONF_ENABLE, 1);
15728c2ecf20Sopenharmony_ci	wbsd_write_config(host, WBSD_CONF_POWER, 0x20);
15738c2ecf20Sopenharmony_ci
15748c2ecf20Sopenharmony_ci	wbsd_lock_config(host);
15758c2ecf20Sopenharmony_ci}
15768c2ecf20Sopenharmony_ci
15778c2ecf20Sopenharmony_ci/*
15788c2ecf20Sopenharmony_ci * Check that configured resources are correct.
15798c2ecf20Sopenharmony_ci */
15808c2ecf20Sopenharmony_ci
15818c2ecf20Sopenharmony_cistatic int wbsd_chip_validate(struct wbsd_host *host)
15828c2ecf20Sopenharmony_ci{
15838c2ecf20Sopenharmony_ci	int base, irq, dma;
15848c2ecf20Sopenharmony_ci
15858c2ecf20Sopenharmony_ci	wbsd_unlock_config(host);
15868c2ecf20Sopenharmony_ci
15878c2ecf20Sopenharmony_ci	/*
15888c2ecf20Sopenharmony_ci	 * Select SD/MMC function.
15898c2ecf20Sopenharmony_ci	 */
15908c2ecf20Sopenharmony_ci	wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD);
15918c2ecf20Sopenharmony_ci
15928c2ecf20Sopenharmony_ci	/*
15938c2ecf20Sopenharmony_ci	 * Read configuration.
15948c2ecf20Sopenharmony_ci	 */
15958c2ecf20Sopenharmony_ci	base = wbsd_read_config(host, WBSD_CONF_PORT_HI) << 8;
15968c2ecf20Sopenharmony_ci	base |= wbsd_read_config(host, WBSD_CONF_PORT_LO);
15978c2ecf20Sopenharmony_ci
15988c2ecf20Sopenharmony_ci	irq = wbsd_read_config(host, WBSD_CONF_IRQ);
15998c2ecf20Sopenharmony_ci
16008c2ecf20Sopenharmony_ci	dma = wbsd_read_config(host, WBSD_CONF_DRQ);
16018c2ecf20Sopenharmony_ci
16028c2ecf20Sopenharmony_ci	wbsd_lock_config(host);
16038c2ecf20Sopenharmony_ci
16048c2ecf20Sopenharmony_ci	/*
16058c2ecf20Sopenharmony_ci	 * Validate against given configuration.
16068c2ecf20Sopenharmony_ci	 */
16078c2ecf20Sopenharmony_ci	if (base != host->base)
16088c2ecf20Sopenharmony_ci		return 0;
16098c2ecf20Sopenharmony_ci	if (irq != host->irq)
16108c2ecf20Sopenharmony_ci		return 0;
16118c2ecf20Sopenharmony_ci	if ((dma != host->dma) && (host->dma != -1))
16128c2ecf20Sopenharmony_ci		return 0;
16138c2ecf20Sopenharmony_ci
16148c2ecf20Sopenharmony_ci	return 1;
16158c2ecf20Sopenharmony_ci}
16168c2ecf20Sopenharmony_ci
16178c2ecf20Sopenharmony_ci/*
16188c2ecf20Sopenharmony_ci * Powers down the SD function
16198c2ecf20Sopenharmony_ci */
16208c2ecf20Sopenharmony_ci
16218c2ecf20Sopenharmony_cistatic void wbsd_chip_poweroff(struct wbsd_host *host)
16228c2ecf20Sopenharmony_ci{
16238c2ecf20Sopenharmony_ci	wbsd_unlock_config(host);
16248c2ecf20Sopenharmony_ci
16258c2ecf20Sopenharmony_ci	wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD);
16268c2ecf20Sopenharmony_ci	wbsd_write_config(host, WBSD_CONF_ENABLE, 0);
16278c2ecf20Sopenharmony_ci
16288c2ecf20Sopenharmony_ci	wbsd_lock_config(host);
16298c2ecf20Sopenharmony_ci}
16308c2ecf20Sopenharmony_ci
16318c2ecf20Sopenharmony_ci/*****************************************************************************\
16328c2ecf20Sopenharmony_ci *                                                                           *
16338c2ecf20Sopenharmony_ci * Devices setup and shutdown                                                *
16348c2ecf20Sopenharmony_ci *                                                                           *
16358c2ecf20Sopenharmony_ci\*****************************************************************************/
16368c2ecf20Sopenharmony_ci
16378c2ecf20Sopenharmony_cistatic int wbsd_init(struct device *dev, int base, int irq, int dma,
16388c2ecf20Sopenharmony_ci	int pnp)
16398c2ecf20Sopenharmony_ci{
16408c2ecf20Sopenharmony_ci	struct wbsd_host *host = NULL;
16418c2ecf20Sopenharmony_ci	struct mmc_host *mmc = NULL;
16428c2ecf20Sopenharmony_ci	int ret;
16438c2ecf20Sopenharmony_ci
16448c2ecf20Sopenharmony_ci	ret = wbsd_alloc_mmc(dev);
16458c2ecf20Sopenharmony_ci	if (ret)
16468c2ecf20Sopenharmony_ci		return ret;
16478c2ecf20Sopenharmony_ci
16488c2ecf20Sopenharmony_ci	mmc = dev_get_drvdata(dev);
16498c2ecf20Sopenharmony_ci	host = mmc_priv(mmc);
16508c2ecf20Sopenharmony_ci
16518c2ecf20Sopenharmony_ci	/*
16528c2ecf20Sopenharmony_ci	 * Scan for hardware.
16538c2ecf20Sopenharmony_ci	 */
16548c2ecf20Sopenharmony_ci	ret = wbsd_scan(host);
16558c2ecf20Sopenharmony_ci	if (ret) {
16568c2ecf20Sopenharmony_ci		if (pnp && (ret == -ENODEV)) {
16578c2ecf20Sopenharmony_ci			pr_warn(DRIVER_NAME ": Unable to confirm device presence - you may experience lock-ups\n");
16588c2ecf20Sopenharmony_ci		} else {
16598c2ecf20Sopenharmony_ci			wbsd_free_mmc(dev);
16608c2ecf20Sopenharmony_ci			return ret;
16618c2ecf20Sopenharmony_ci		}
16628c2ecf20Sopenharmony_ci	}
16638c2ecf20Sopenharmony_ci
16648c2ecf20Sopenharmony_ci	/*
16658c2ecf20Sopenharmony_ci	 * Request resources.
16668c2ecf20Sopenharmony_ci	 */
16678c2ecf20Sopenharmony_ci	ret = wbsd_request_resources(host, base, irq, dma);
16688c2ecf20Sopenharmony_ci	if (ret) {
16698c2ecf20Sopenharmony_ci		wbsd_release_resources(host);
16708c2ecf20Sopenharmony_ci		wbsd_free_mmc(dev);
16718c2ecf20Sopenharmony_ci		return ret;
16728c2ecf20Sopenharmony_ci	}
16738c2ecf20Sopenharmony_ci
16748c2ecf20Sopenharmony_ci	/*
16758c2ecf20Sopenharmony_ci	 * See if chip needs to be configured.
16768c2ecf20Sopenharmony_ci	 */
16778c2ecf20Sopenharmony_ci	if (pnp) {
16788c2ecf20Sopenharmony_ci		if ((host->config != 0) && !wbsd_chip_validate(host)) {
16798c2ecf20Sopenharmony_ci			pr_warn(DRIVER_NAME ": PnP active but chip not configured! You probably have a buggy BIOS. Configuring chip manually.\n");
16808c2ecf20Sopenharmony_ci			wbsd_chip_config(host);
16818c2ecf20Sopenharmony_ci		}
16828c2ecf20Sopenharmony_ci	} else
16838c2ecf20Sopenharmony_ci		wbsd_chip_config(host);
16848c2ecf20Sopenharmony_ci
16858c2ecf20Sopenharmony_ci	/*
16868c2ecf20Sopenharmony_ci	 * Power Management stuff. No idea how this works.
16878c2ecf20Sopenharmony_ci	 * Not tested.
16888c2ecf20Sopenharmony_ci	 */
16898c2ecf20Sopenharmony_ci#ifdef CONFIG_PM
16908c2ecf20Sopenharmony_ci	if (host->config) {
16918c2ecf20Sopenharmony_ci		wbsd_unlock_config(host);
16928c2ecf20Sopenharmony_ci		wbsd_write_config(host, WBSD_CONF_PME, 0xA0);
16938c2ecf20Sopenharmony_ci		wbsd_lock_config(host);
16948c2ecf20Sopenharmony_ci	}
16958c2ecf20Sopenharmony_ci#endif
16968c2ecf20Sopenharmony_ci	/*
16978c2ecf20Sopenharmony_ci	 * Allow device to initialise itself properly.
16988c2ecf20Sopenharmony_ci	 */
16998c2ecf20Sopenharmony_ci	mdelay(5);
17008c2ecf20Sopenharmony_ci
17018c2ecf20Sopenharmony_ci	/*
17028c2ecf20Sopenharmony_ci	 * Reset the chip into a known state.
17038c2ecf20Sopenharmony_ci	 */
17048c2ecf20Sopenharmony_ci	wbsd_init_device(host);
17058c2ecf20Sopenharmony_ci
17068c2ecf20Sopenharmony_ci	ret = mmc_add_host(mmc);
17078c2ecf20Sopenharmony_ci	if (ret) {
17088c2ecf20Sopenharmony_ci		if (!pnp)
17098c2ecf20Sopenharmony_ci			wbsd_chip_poweroff(host);
17108c2ecf20Sopenharmony_ci
17118c2ecf20Sopenharmony_ci		wbsd_release_resources(host);
17128c2ecf20Sopenharmony_ci		wbsd_free_mmc(dev);
17138c2ecf20Sopenharmony_ci		return ret;
17148c2ecf20Sopenharmony_ci	}
17158c2ecf20Sopenharmony_ci
17168c2ecf20Sopenharmony_ci	pr_info("%s: W83L51xD", mmc_hostname(mmc));
17178c2ecf20Sopenharmony_ci	if (host->chip_id != 0)
17188c2ecf20Sopenharmony_ci		printk(" id %x", (int)host->chip_id);
17198c2ecf20Sopenharmony_ci	printk(" at 0x%x irq %d", (int)host->base, (int)host->irq);
17208c2ecf20Sopenharmony_ci	if (host->dma >= 0)
17218c2ecf20Sopenharmony_ci		printk(" dma %d", (int)host->dma);
17228c2ecf20Sopenharmony_ci	else
17238c2ecf20Sopenharmony_ci		printk(" FIFO");
17248c2ecf20Sopenharmony_ci	if (pnp)
17258c2ecf20Sopenharmony_ci		printk(" PnP");
17268c2ecf20Sopenharmony_ci	printk("\n");
17278c2ecf20Sopenharmony_ci
17288c2ecf20Sopenharmony_ci	return 0;
17298c2ecf20Sopenharmony_ci}
17308c2ecf20Sopenharmony_ci
17318c2ecf20Sopenharmony_cistatic void wbsd_shutdown(struct device *dev, int pnp)
17328c2ecf20Sopenharmony_ci{
17338c2ecf20Sopenharmony_ci	struct mmc_host *mmc = dev_get_drvdata(dev);
17348c2ecf20Sopenharmony_ci	struct wbsd_host *host;
17358c2ecf20Sopenharmony_ci
17368c2ecf20Sopenharmony_ci	if (!mmc)
17378c2ecf20Sopenharmony_ci		return;
17388c2ecf20Sopenharmony_ci
17398c2ecf20Sopenharmony_ci	host = mmc_priv(mmc);
17408c2ecf20Sopenharmony_ci
17418c2ecf20Sopenharmony_ci	mmc_remove_host(mmc);
17428c2ecf20Sopenharmony_ci
17438c2ecf20Sopenharmony_ci	/*
17448c2ecf20Sopenharmony_ci	 * Power down the SD/MMC function.
17458c2ecf20Sopenharmony_ci	 */
17468c2ecf20Sopenharmony_ci	if (!pnp)
17478c2ecf20Sopenharmony_ci		wbsd_chip_poweroff(host);
17488c2ecf20Sopenharmony_ci
17498c2ecf20Sopenharmony_ci	wbsd_release_resources(host);
17508c2ecf20Sopenharmony_ci
17518c2ecf20Sopenharmony_ci	wbsd_free_mmc(dev);
17528c2ecf20Sopenharmony_ci}
17538c2ecf20Sopenharmony_ci
17548c2ecf20Sopenharmony_ci/*
17558c2ecf20Sopenharmony_ci * Non-PnP
17568c2ecf20Sopenharmony_ci */
17578c2ecf20Sopenharmony_ci
17588c2ecf20Sopenharmony_cistatic int wbsd_probe(struct platform_device *dev)
17598c2ecf20Sopenharmony_ci{
17608c2ecf20Sopenharmony_ci	/* Use the module parameters for resources */
17618c2ecf20Sopenharmony_ci	return wbsd_init(&dev->dev, param_io, param_irq, param_dma, 0);
17628c2ecf20Sopenharmony_ci}
17638c2ecf20Sopenharmony_ci
17648c2ecf20Sopenharmony_cistatic int wbsd_remove(struct platform_device *dev)
17658c2ecf20Sopenharmony_ci{
17668c2ecf20Sopenharmony_ci	wbsd_shutdown(&dev->dev, 0);
17678c2ecf20Sopenharmony_ci
17688c2ecf20Sopenharmony_ci	return 0;
17698c2ecf20Sopenharmony_ci}
17708c2ecf20Sopenharmony_ci
17718c2ecf20Sopenharmony_ci/*
17728c2ecf20Sopenharmony_ci * PnP
17738c2ecf20Sopenharmony_ci */
17748c2ecf20Sopenharmony_ci
17758c2ecf20Sopenharmony_ci#ifdef CONFIG_PNP
17768c2ecf20Sopenharmony_ci
17778c2ecf20Sopenharmony_cistatic int
17788c2ecf20Sopenharmony_ciwbsd_pnp_probe(struct pnp_dev *pnpdev, const struct pnp_device_id *dev_id)
17798c2ecf20Sopenharmony_ci{
17808c2ecf20Sopenharmony_ci	int io, irq, dma;
17818c2ecf20Sopenharmony_ci
17828c2ecf20Sopenharmony_ci	/*
17838c2ecf20Sopenharmony_ci	 * Get resources from PnP layer.
17848c2ecf20Sopenharmony_ci	 */
17858c2ecf20Sopenharmony_ci	io = pnp_port_start(pnpdev, 0);
17868c2ecf20Sopenharmony_ci	irq = pnp_irq(pnpdev, 0);
17878c2ecf20Sopenharmony_ci	if (pnp_dma_valid(pnpdev, 0))
17888c2ecf20Sopenharmony_ci		dma = pnp_dma(pnpdev, 0);
17898c2ecf20Sopenharmony_ci	else
17908c2ecf20Sopenharmony_ci		dma = -1;
17918c2ecf20Sopenharmony_ci
17928c2ecf20Sopenharmony_ci	DBGF("PnP resources: port %3x irq %d dma %d\n", io, irq, dma);
17938c2ecf20Sopenharmony_ci
17948c2ecf20Sopenharmony_ci	return wbsd_init(&pnpdev->dev, io, irq, dma, 1);
17958c2ecf20Sopenharmony_ci}
17968c2ecf20Sopenharmony_ci
17978c2ecf20Sopenharmony_cistatic void wbsd_pnp_remove(struct pnp_dev *dev)
17988c2ecf20Sopenharmony_ci{
17998c2ecf20Sopenharmony_ci	wbsd_shutdown(&dev->dev, 1);
18008c2ecf20Sopenharmony_ci}
18018c2ecf20Sopenharmony_ci
18028c2ecf20Sopenharmony_ci#endif /* CONFIG_PNP */
18038c2ecf20Sopenharmony_ci
18048c2ecf20Sopenharmony_ci/*
18058c2ecf20Sopenharmony_ci * Power management
18068c2ecf20Sopenharmony_ci */
18078c2ecf20Sopenharmony_ci
18088c2ecf20Sopenharmony_ci#ifdef CONFIG_PM
18098c2ecf20Sopenharmony_ci
18108c2ecf20Sopenharmony_cistatic int wbsd_platform_suspend(struct platform_device *dev,
18118c2ecf20Sopenharmony_ci				 pm_message_t state)
18128c2ecf20Sopenharmony_ci{
18138c2ecf20Sopenharmony_ci	struct mmc_host *mmc = platform_get_drvdata(dev);
18148c2ecf20Sopenharmony_ci	struct wbsd_host *host;
18158c2ecf20Sopenharmony_ci
18168c2ecf20Sopenharmony_ci	if (mmc == NULL)
18178c2ecf20Sopenharmony_ci		return 0;
18188c2ecf20Sopenharmony_ci
18198c2ecf20Sopenharmony_ci	DBGF("Suspending...\n");
18208c2ecf20Sopenharmony_ci
18218c2ecf20Sopenharmony_ci	host = mmc_priv(mmc);
18228c2ecf20Sopenharmony_ci
18238c2ecf20Sopenharmony_ci	wbsd_chip_poweroff(host);
18248c2ecf20Sopenharmony_ci	return 0;
18258c2ecf20Sopenharmony_ci}
18268c2ecf20Sopenharmony_ci
18278c2ecf20Sopenharmony_cistatic int wbsd_platform_resume(struct platform_device *dev)
18288c2ecf20Sopenharmony_ci{
18298c2ecf20Sopenharmony_ci	struct mmc_host *mmc = platform_get_drvdata(dev);
18308c2ecf20Sopenharmony_ci	struct wbsd_host *host;
18318c2ecf20Sopenharmony_ci
18328c2ecf20Sopenharmony_ci	if (mmc == NULL)
18338c2ecf20Sopenharmony_ci		return 0;
18348c2ecf20Sopenharmony_ci
18358c2ecf20Sopenharmony_ci	DBGF("Resuming...\n");
18368c2ecf20Sopenharmony_ci
18378c2ecf20Sopenharmony_ci	host = mmc_priv(mmc);
18388c2ecf20Sopenharmony_ci
18398c2ecf20Sopenharmony_ci	wbsd_chip_config(host);
18408c2ecf20Sopenharmony_ci
18418c2ecf20Sopenharmony_ci	/*
18428c2ecf20Sopenharmony_ci	 * Allow device to initialise itself properly.
18438c2ecf20Sopenharmony_ci	 */
18448c2ecf20Sopenharmony_ci	mdelay(5);
18458c2ecf20Sopenharmony_ci
18468c2ecf20Sopenharmony_ci	wbsd_init_device(host);
18478c2ecf20Sopenharmony_ci	return 0;
18488c2ecf20Sopenharmony_ci}
18498c2ecf20Sopenharmony_ci
18508c2ecf20Sopenharmony_ci#ifdef CONFIG_PNP
18518c2ecf20Sopenharmony_ci
18528c2ecf20Sopenharmony_cistatic int wbsd_pnp_suspend(struct pnp_dev *pnp_dev, pm_message_t state)
18538c2ecf20Sopenharmony_ci{
18548c2ecf20Sopenharmony_ci	struct mmc_host *mmc = dev_get_drvdata(&pnp_dev->dev);
18558c2ecf20Sopenharmony_ci
18568c2ecf20Sopenharmony_ci	if (mmc == NULL)
18578c2ecf20Sopenharmony_ci		return 0;
18588c2ecf20Sopenharmony_ci
18598c2ecf20Sopenharmony_ci	DBGF("Suspending...\n");
18608c2ecf20Sopenharmony_ci	return 0;
18618c2ecf20Sopenharmony_ci}
18628c2ecf20Sopenharmony_ci
18638c2ecf20Sopenharmony_cistatic int wbsd_pnp_resume(struct pnp_dev *pnp_dev)
18648c2ecf20Sopenharmony_ci{
18658c2ecf20Sopenharmony_ci	struct mmc_host *mmc = dev_get_drvdata(&pnp_dev->dev);
18668c2ecf20Sopenharmony_ci	struct wbsd_host *host;
18678c2ecf20Sopenharmony_ci
18688c2ecf20Sopenharmony_ci	if (mmc == NULL)
18698c2ecf20Sopenharmony_ci		return 0;
18708c2ecf20Sopenharmony_ci
18718c2ecf20Sopenharmony_ci	DBGF("Resuming...\n");
18728c2ecf20Sopenharmony_ci
18738c2ecf20Sopenharmony_ci	host = mmc_priv(mmc);
18748c2ecf20Sopenharmony_ci
18758c2ecf20Sopenharmony_ci	/*
18768c2ecf20Sopenharmony_ci	 * See if chip needs to be configured.
18778c2ecf20Sopenharmony_ci	 */
18788c2ecf20Sopenharmony_ci	if (host->config != 0) {
18798c2ecf20Sopenharmony_ci		if (!wbsd_chip_validate(host)) {
18808c2ecf20Sopenharmony_ci			pr_warn(DRIVER_NAME ": PnP active but chip not configured! You probably have a buggy BIOS. Configuring chip manually.\n");
18818c2ecf20Sopenharmony_ci			wbsd_chip_config(host);
18828c2ecf20Sopenharmony_ci		}
18838c2ecf20Sopenharmony_ci	}
18848c2ecf20Sopenharmony_ci
18858c2ecf20Sopenharmony_ci	/*
18868c2ecf20Sopenharmony_ci	 * Allow device to initialise itself properly.
18878c2ecf20Sopenharmony_ci	 */
18888c2ecf20Sopenharmony_ci	mdelay(5);
18898c2ecf20Sopenharmony_ci
18908c2ecf20Sopenharmony_ci	wbsd_init_device(host);
18918c2ecf20Sopenharmony_ci	return 0;
18928c2ecf20Sopenharmony_ci}
18938c2ecf20Sopenharmony_ci
18948c2ecf20Sopenharmony_ci#endif /* CONFIG_PNP */
18958c2ecf20Sopenharmony_ci
18968c2ecf20Sopenharmony_ci#else /* CONFIG_PM */
18978c2ecf20Sopenharmony_ci
18988c2ecf20Sopenharmony_ci#define wbsd_platform_suspend NULL
18998c2ecf20Sopenharmony_ci#define wbsd_platform_resume NULL
19008c2ecf20Sopenharmony_ci
19018c2ecf20Sopenharmony_ci#define wbsd_pnp_suspend NULL
19028c2ecf20Sopenharmony_ci#define wbsd_pnp_resume NULL
19038c2ecf20Sopenharmony_ci
19048c2ecf20Sopenharmony_ci#endif /* CONFIG_PM */
19058c2ecf20Sopenharmony_ci
19068c2ecf20Sopenharmony_cistatic struct platform_device *wbsd_device;
19078c2ecf20Sopenharmony_ci
19088c2ecf20Sopenharmony_cistatic struct platform_driver wbsd_driver = {
19098c2ecf20Sopenharmony_ci	.probe		= wbsd_probe,
19108c2ecf20Sopenharmony_ci	.remove		= wbsd_remove,
19118c2ecf20Sopenharmony_ci
19128c2ecf20Sopenharmony_ci	.suspend	= wbsd_platform_suspend,
19138c2ecf20Sopenharmony_ci	.resume		= wbsd_platform_resume,
19148c2ecf20Sopenharmony_ci	.driver		= {
19158c2ecf20Sopenharmony_ci		.name	= DRIVER_NAME,
19168c2ecf20Sopenharmony_ci		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
19178c2ecf20Sopenharmony_ci	},
19188c2ecf20Sopenharmony_ci};
19198c2ecf20Sopenharmony_ci
19208c2ecf20Sopenharmony_ci#ifdef CONFIG_PNP
19218c2ecf20Sopenharmony_ci
19228c2ecf20Sopenharmony_cistatic struct pnp_driver wbsd_pnp_driver = {
19238c2ecf20Sopenharmony_ci	.name		= DRIVER_NAME,
19248c2ecf20Sopenharmony_ci	.id_table	= pnp_dev_table,
19258c2ecf20Sopenharmony_ci	.probe		= wbsd_pnp_probe,
19268c2ecf20Sopenharmony_ci	.remove		= wbsd_pnp_remove,
19278c2ecf20Sopenharmony_ci
19288c2ecf20Sopenharmony_ci	.suspend	= wbsd_pnp_suspend,
19298c2ecf20Sopenharmony_ci	.resume		= wbsd_pnp_resume,
19308c2ecf20Sopenharmony_ci};
19318c2ecf20Sopenharmony_ci
19328c2ecf20Sopenharmony_ci#endif /* CONFIG_PNP */
19338c2ecf20Sopenharmony_ci
19348c2ecf20Sopenharmony_ci/*
19358c2ecf20Sopenharmony_ci * Module loading/unloading
19368c2ecf20Sopenharmony_ci */
19378c2ecf20Sopenharmony_ci
19388c2ecf20Sopenharmony_cistatic int __init wbsd_drv_init(void)
19398c2ecf20Sopenharmony_ci{
19408c2ecf20Sopenharmony_ci	int result;
19418c2ecf20Sopenharmony_ci
19428c2ecf20Sopenharmony_ci	pr_info(DRIVER_NAME
19438c2ecf20Sopenharmony_ci		": Winbond W83L51xD SD/MMC card interface driver\n");
19448c2ecf20Sopenharmony_ci	pr_info(DRIVER_NAME ": Copyright(c) Pierre Ossman\n");
19458c2ecf20Sopenharmony_ci
19468c2ecf20Sopenharmony_ci#ifdef CONFIG_PNP
19478c2ecf20Sopenharmony_ci
19488c2ecf20Sopenharmony_ci	if (!param_nopnp) {
19498c2ecf20Sopenharmony_ci		result = pnp_register_driver(&wbsd_pnp_driver);
19508c2ecf20Sopenharmony_ci		if (result < 0)
19518c2ecf20Sopenharmony_ci			return result;
19528c2ecf20Sopenharmony_ci	}
19538c2ecf20Sopenharmony_ci#endif /* CONFIG_PNP */
19548c2ecf20Sopenharmony_ci
19558c2ecf20Sopenharmony_ci	if (param_nopnp) {
19568c2ecf20Sopenharmony_ci		result = platform_driver_register(&wbsd_driver);
19578c2ecf20Sopenharmony_ci		if (result < 0)
19588c2ecf20Sopenharmony_ci			return result;
19598c2ecf20Sopenharmony_ci
19608c2ecf20Sopenharmony_ci		wbsd_device = platform_device_alloc(DRIVER_NAME, -1);
19618c2ecf20Sopenharmony_ci		if (!wbsd_device) {
19628c2ecf20Sopenharmony_ci			platform_driver_unregister(&wbsd_driver);
19638c2ecf20Sopenharmony_ci			return -ENOMEM;
19648c2ecf20Sopenharmony_ci		}
19658c2ecf20Sopenharmony_ci
19668c2ecf20Sopenharmony_ci		result = platform_device_add(wbsd_device);
19678c2ecf20Sopenharmony_ci		if (result) {
19688c2ecf20Sopenharmony_ci			platform_device_put(wbsd_device);
19698c2ecf20Sopenharmony_ci			platform_driver_unregister(&wbsd_driver);
19708c2ecf20Sopenharmony_ci			return result;
19718c2ecf20Sopenharmony_ci		}
19728c2ecf20Sopenharmony_ci	}
19738c2ecf20Sopenharmony_ci
19748c2ecf20Sopenharmony_ci	return 0;
19758c2ecf20Sopenharmony_ci}
19768c2ecf20Sopenharmony_ci
19778c2ecf20Sopenharmony_cistatic void __exit wbsd_drv_exit(void)
19788c2ecf20Sopenharmony_ci{
19798c2ecf20Sopenharmony_ci#ifdef CONFIG_PNP
19808c2ecf20Sopenharmony_ci
19818c2ecf20Sopenharmony_ci	if (!param_nopnp)
19828c2ecf20Sopenharmony_ci		pnp_unregister_driver(&wbsd_pnp_driver);
19838c2ecf20Sopenharmony_ci
19848c2ecf20Sopenharmony_ci#endif /* CONFIG_PNP */
19858c2ecf20Sopenharmony_ci
19868c2ecf20Sopenharmony_ci	if (param_nopnp) {
19878c2ecf20Sopenharmony_ci		platform_device_unregister(wbsd_device);
19888c2ecf20Sopenharmony_ci
19898c2ecf20Sopenharmony_ci		platform_driver_unregister(&wbsd_driver);
19908c2ecf20Sopenharmony_ci	}
19918c2ecf20Sopenharmony_ci
19928c2ecf20Sopenharmony_ci	DBG("unloaded\n");
19938c2ecf20Sopenharmony_ci}
19948c2ecf20Sopenharmony_ci
19958c2ecf20Sopenharmony_cimodule_init(wbsd_drv_init);
19968c2ecf20Sopenharmony_cimodule_exit(wbsd_drv_exit);
19978c2ecf20Sopenharmony_ci#ifdef CONFIG_PNP
19988c2ecf20Sopenharmony_cimodule_param_hw_named(nopnp, param_nopnp, uint, other, 0444);
19998c2ecf20Sopenharmony_ci#endif
20008c2ecf20Sopenharmony_cimodule_param_hw_named(io, param_io, uint, ioport, 0444);
20018c2ecf20Sopenharmony_cimodule_param_hw_named(irq, param_irq, uint, irq, 0444);
20028c2ecf20Sopenharmony_cimodule_param_hw_named(dma, param_dma, int, dma, 0444);
20038c2ecf20Sopenharmony_ci
20048c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
20058c2ecf20Sopenharmony_ciMODULE_AUTHOR("Pierre Ossman <pierre@ossman.eu>");
20068c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Winbond W83L51xD SD/MMC card interface driver");
20078c2ecf20Sopenharmony_ci
20088c2ecf20Sopenharmony_ci#ifdef CONFIG_PNP
20098c2ecf20Sopenharmony_ciMODULE_PARM_DESC(nopnp, "Scan for device instead of relying on PNP. (default 0)");
20108c2ecf20Sopenharmony_ci#endif
20118c2ecf20Sopenharmony_ciMODULE_PARM_DESC(io, "I/O base to allocate. Must be 8 byte aligned. (default 0x248)");
20128c2ecf20Sopenharmony_ciMODULE_PARM_DESC(irq, "IRQ to allocate. (default 6)");
20138c2ecf20Sopenharmony_ciMODULE_PARM_DESC(dma, "DMA channel to allocate. -1 for no DMA. (default 2)");
2014