Skip to content
Navigation Menu
Toggle navigation
Sign in
In this repository
All GitHub Enterprise
↵
Jump to
↵
No suggested jump to results
In this repository
All GitHub Enterprise
↵
Jump to
↵
In this organization
All GitHub Enterprise
↵
Jump to
↵
In this repository
All GitHub Enterprise
↵
Jump to
↵
Sign in
Reseting focus
You signed in with another tab or window.
Reload
to refresh your session.
You signed out in another tab or window.
Reload
to refresh your session.
You switched accounts on another tab or window.
Reload
to refresh your session.
Dismiss alert
{{ message }}
mariux64
/
linux
Public
Notifications
You must be signed in to change notification settings
Fork
0
Star
0
Code
Issues
1
Pull requests
0
Actions
Projects
0
Wiki
Security
Insights
Additional navigation options
Code
Issues
Pull requests
Actions
Projects
Wiki
Security
Insights
Files
d830354
Documentation
LICENSES
arch
block
certs
crypto
drivers
accel
accessibility
acpi
amba
android
ata
atm
auxdisplay
base
bcma
block
bluetooth
bus
cdrom
cdx
char
clk
clocksource
comedi
connector
counter
cpufreq
cpuidle
crypto
cxl
dax
dca
devfreq
dio
dma-buf
dma
edac
eisa
extcon
firewire
firmware
fpga
fsi
gnss
gpio
gpu
greybus
hid
hsi
hte
hv
hwmon
hwspinlock
hwtracing
i2c
i3c
idle
iio
infiniband
input
interconnect
iommu
ipack
irqchip
isdn
leds
macintosh
mailbox
mcb
md
media
memory
memstick
message
mfd
misc
mmc
core
host
Kconfig
Makefile
alcor.c
atmel-mci.c
au1xmmc.c
bcm2835.c
cavium-octeon.c
cavium-thunderx.c
cavium.c
cavium.h
cb710-mmc.c
cb710-mmc.h
cqhci-core.c
cqhci-crypto.c
cqhci-crypto.h
cqhci.h
davinci_mmc.c
dw_mmc-bluefield.c
dw_mmc-exynos.c
dw_mmc-exynos.h
dw_mmc-hi3798cv200.c
dw_mmc-k3.c
dw_mmc-pci.c
dw_mmc-pltfm.c
dw_mmc-pltfm.h
dw_mmc-rockchip.c
dw_mmc-starfive.c
dw_mmc.c
dw_mmc.h
jz4740_mmc.c
litex_mmc.c
meson-gx-mmc.c
meson-mx-sdhc-clkc.c
meson-mx-sdhc-mmc.c
meson-mx-sdhc.h
meson-mx-sdio.c
mmc_hsq.c
mmc_hsq.h
mmc_spi.c
mmci.c
mmci.h
mmci_qcom_dml.c
mmci_stm32_sdmmc.c
moxart-mmc.c
mtk-sd.c
mvsdio.c
mvsdio.h
mxcmmc.c
mxs-mmc.c
of_mmc_spi.c
omap.c
omap_hsmmc.c
owl-mmc.c
pxamci.c
pxamci.h
renesas_sdhi.h
renesas_sdhi_core.c
renesas_sdhi_internal_dmac.c
renesas_sdhi_sys_dmac.c
rtsx_pci_sdmmc.c
rtsx_usb_sdmmc.c
sdhci-acpi.c
sdhci-bcm-kona.c
sdhci-brcmstb.c
sdhci-cadence.c
sdhci-cqhci.h
sdhci-dove.c
sdhci-esdhc-imx.c
sdhci-esdhc-mcf.c
sdhci-esdhc.h
sdhci-iproc.c
sdhci-milbeaut.c
sdhci-msm.c
sdhci-of-arasan.c
sdhci-of-aspeed-test.c
sdhci-of-aspeed.c
sdhci-of-at91.c
sdhci-of-dwcmshc.c
sdhci-of-esdhc.c
sdhci-of-hlwd.c
sdhci-of-sparx5.c
sdhci-omap.c
sdhci-pci-arasan.c
sdhci-pci-core.c
sdhci-pci-dwc-mshc.c
sdhci-pci-gli.c
sdhci-pci-o2micro.c
sdhci-pci.h
sdhci-pic32.c
sdhci-pltfm.c
sdhci-pltfm.h
sdhci-pxav2.c
sdhci-pxav3.c
sdhci-s3c.c
sdhci-spear.c
sdhci-sprd.c
sdhci-st.c
sdhci-tegra.c
sdhci-xenon-phy.c
sdhci-xenon.c
sdhci-xenon.h
sdhci.c
sdhci.h
sdhci_am654.c
sdhci_f_sdh30.c
sdhci_f_sdh30.h
sdricoh_cs.c
sh_mmcif.c
sunplus-mmc.c
sunxi-mmc.c
tifm_sd.c
tmio_mmc.h
tmio_mmc_core.c
toshsd.c
toshsd.h
uniphier-sd.c
usdhi6rol0.c
ushc.c
via-sdmmc.c
vub300.c
wbsd.c
wbsd.h
wmt-sdmmc.c
Kconfig
Makefile
most
mtd
mux
net
nfc
ntb
nubus
nvdimm
nvme
nvmem
of
opp
parisc
parport
pci
pcmcia
peci
perf
phy
pinctrl
platform
pnp
power
powercap
pps
ps3
ptp
pwm
rapidio
ras
regulator
remoteproc
reset
rpmsg
rtc
s390
sbus
scsi
sh
siox
slimbus
soc
soundwire
spi
spmi
ssb
staging
target
tc
tee
thermal
thunderbolt
tty
ufs
uio
usb
vdpa
vfio
vhost
video
virt
virtio
vlynq
w1
watchdog
xen
zorro
Kconfig
Makefile
fs
include
init
io_uring
ipc
kernel
lib
mm
net
rust
samples
scripts
security
sound
tools
usr
virt
.clang-format
.cocciconfig
.get_maintainer.ignore
.gitattributes
.gitignore
.mailmap
.rustfmt.toml
COPYING
CREDITS
Kbuild
Kconfig
MAINTAINERS
Makefile
README
Breadcrumbs
linux
/
drivers
/
mmc
/
host
/
wbsd.c
Copy path
Blame
Blame
Latest commit
History
History
2008 lines (1605 loc) · 39.4 KB
Breadcrumbs
linux
/
drivers
/
mmc
/
host
/
wbsd.c
Top
File metadata and controls
Code
Blame
2008 lines (1605 loc) · 39.4 KB
Raw
// SPDX-License-Identifier: GPL-2.0-or-later /* * linux/drivers/mmc/host/wbsd.c - Winbond W83L51xD SD/MMC driver * * Copyright (C) 2004-2007 Pierre Ossman, All Rights Reserved. * * Warning! * * Changes to the FIFO system should be done with extreme care since * the hardware is full of bugs related to the FIFO. Known issues are: * * - FIFO size field in FSR is always zero. * * - FIFO interrupts tend not to work as they should. Interrupts are * triggered only for full/empty events, not for threshold values. * * - On APIC systems the FIFO empty interrupt is sometimes lost. */ #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/ioport.h> #include <linux/platform_device.h> #include <linux/interrupt.h> #include <linux/dma-mapping.h> #include <linux/delay.h> #include <linux/pnp.h> #include <linux/highmem.h> #include <linux/mmc/host.h> #include <linux/mmc/mmc.h> #include <linux/mmc/sd.h> #include <linux/scatterlist.h> #include <linux/slab.h> #include <asm/io.h> #include <asm/dma.h> #include "wbsd.h" #define DRIVER_NAME "wbsd" #define DBG(x...) \ pr_debug(DRIVER_NAME ": " x) #define DBGF(f, x...) \ pr_debug(DRIVER_NAME " [%s()]: " f, __func__ , ##x) /* * Device resources */ #ifdef CONFIG_PNP static const struct pnp_device_id pnp_dev_table[] = { { "WEC0517", 0 }, { "WEC0518", 0 }, { "", 0 }, }; MODULE_DEVICE_TABLE(pnp, pnp_dev_table); #endif /* CONFIG_PNP */ static const int config_ports[] = { 0x2E, 0x4E }; static const int unlock_codes[] = { 0x83, 0x87 }; static const int valid_ids[] = { 0x7112, }; #ifdef CONFIG_PNP static unsigned int param_nopnp = 0; #else static const unsigned int param_nopnp = 1; #endif static unsigned int param_io = 0x248; static unsigned int param_irq = 6; static int param_dma = 2; /* * Basic functions */ static inline void wbsd_unlock_config(struct wbsd_host *host) { BUG_ON(host->config == 0); outb(host->unlock_code, host->config); outb(host->unlock_code, host->config); } static inline void wbsd_lock_config(struct wbsd_host *host) { BUG_ON(host->config == 0); outb(LOCK_CODE, host->config); } static inline void wbsd_write_config(struct wbsd_host *host, u8 reg, u8 value) { BUG_ON(host->config == 0); outb(reg, host->config); outb(value, host->config + 1); } static inline u8 wbsd_read_config(struct wbsd_host *host, u8 reg) { BUG_ON(host->config == 0); outb(reg, host->config); return inb(host->config + 1); } static inline void wbsd_write_index(struct wbsd_host *host, u8 index, u8 value) { outb(index, host->base + WBSD_IDXR); outb(value, host->base + WBSD_DATAR); } static inline u8 wbsd_read_index(struct wbsd_host *host, u8 index) { outb(index, host->base + WBSD_IDXR); return inb(host->base + WBSD_DATAR); } /* * Common routines */ static void wbsd_init_device(struct wbsd_host *host) { u8 setup, ier; /* * Reset chip (SD/MMC part) and fifo. */ setup = wbsd_read_index(host, WBSD_IDX_SETUP); setup |= WBSD_FIFO_RESET | WBSD_SOFT_RESET; wbsd_write_index(host, WBSD_IDX_SETUP, setup); /* * Set DAT3 to input */ setup &= ~WBSD_DAT3_H; wbsd_write_index(host, WBSD_IDX_SETUP, setup); host->flags &= ~WBSD_FIGNORE_DETECT; /* * Read back default clock. */ host->clk = wbsd_read_index(host, WBSD_IDX_CLK); /* * Power down port. */ outb(WBSD_POWER_N, host->base + WBSD_CSR); /* * Set maximum timeout. */ wbsd_write_index(host, WBSD_IDX_TAAC, 0x7F); /* * Test for card presence */ if (inb(host->base + WBSD_CSR) & WBSD_CARDPRESENT) host->flags |= WBSD_FCARD_PRESENT; else host->flags &= ~WBSD_FCARD_PRESENT; /* * Enable interesting interrupts. */ ier = 0; ier |= WBSD_EINT_CARD; ier |= WBSD_EINT_FIFO_THRE; ier |= WBSD_EINT_CRC; ier |= WBSD_EINT_TIMEOUT; ier |= WBSD_EINT_TC; outb(ier, host->base + WBSD_EIR); /* * Clear interrupts. */ inb(host->base + WBSD_ISR); } static void wbsd_reset(struct wbsd_host *host) { u8 setup; pr_err("%s: Resetting chip\n", mmc_hostname(host->mmc)); /* * Soft reset of chip (SD/MMC part). */ setup = wbsd_read_index(host, WBSD_IDX_SETUP); setup |= WBSD_SOFT_RESET; wbsd_write_index(host, WBSD_IDX_SETUP, setup); } static void wbsd_request_end(struct wbsd_host *host, struct mmc_request *mrq) { unsigned long dmaflags; if (host->dma >= 0) { /* * Release ISA DMA controller. */ dmaflags = claim_dma_lock(); disable_dma(host->dma); clear_dma_ff(host->dma); release_dma_lock(dmaflags); /* * Disable DMA on host. */ wbsd_write_index(host, WBSD_IDX_DMA, 0); } host->mrq = NULL; /* * MMC layer might call back into the driver so first unlock. */ spin_unlock(&host->lock); mmc_request_done(host->mmc, mrq); spin_lock(&host->lock); } /* * Scatter/gather functions */ static inline void wbsd_init_sg(struct wbsd_host *host, struct mmc_data *data) { /* * Get info. about SG list from data structure. */ host->cur_sg = data->sg; host->num_sg = data->sg_len; host->offset = 0; host->remain = host->cur_sg->length; } static inline int wbsd_next_sg(struct wbsd_host *host) { /* * Skip to next SG entry. */ host->cur_sg++; host->num_sg--; /* * Any entries left? */ if (host->num_sg > 0) { host->offset = 0; host->remain = host->cur_sg->length; } return host->num_sg; } static inline char *wbsd_map_sg(struct wbsd_host *host) { return kmap_local_page(sg_page(host->cur_sg)) + host->cur_sg->offset; } static inline void wbsd_sg_to_dma(struct wbsd_host *host, struct mmc_data *data) { size_t len = 0; int i; for (i = 0; i < data->sg_len; i++) len += data->sg[i].length; sg_copy_to_buffer(data->sg, data->sg_len, host->dma_buffer, len); } static inline void wbsd_dma_to_sg(struct wbsd_host *host, struct mmc_data *data) { size_t len = 0; int i; for (i = 0; i < data->sg_len; i++) len += data->sg[i].length; sg_copy_from_buffer(data->sg, data->sg_len, host->dma_buffer, len); } /* * Command handling */ static inline void wbsd_get_short_reply(struct wbsd_host *host, struct mmc_command *cmd) { /* * Correct response type? */ if (wbsd_read_index(host, WBSD_IDX_RSPLEN) != WBSD_RSP_SHORT) { cmd->error = -EILSEQ; return; } cmd->resp[0] = wbsd_read_index(host, WBSD_IDX_RESP12) << 24; cmd->resp[0] |= wbsd_read_index(host, WBSD_IDX_RESP13) << 16; cmd->resp[0] |= wbsd_read_index(host, WBSD_IDX_RESP14) << 8; cmd->resp[0] |= wbsd_read_index(host, WBSD_IDX_RESP15) << 0; cmd->resp[1] = wbsd_read_index(host, WBSD_IDX_RESP16) << 24; } static inline void wbsd_get_long_reply(struct wbsd_host *host, struct mmc_command *cmd) { int i; /* * Correct response type? */ if (wbsd_read_index(host, WBSD_IDX_RSPLEN) != WBSD_RSP_LONG) { cmd->error = -EILSEQ; return; } for (i = 0; i < 4; i++) { cmd->resp[i] = wbsd_read_index(host, WBSD_IDX_RESP1 + i * 4) << 24; cmd->resp[i] |= wbsd_read_index(host, WBSD_IDX_RESP2 + i * 4) << 16; cmd->resp[i] |= wbsd_read_index(host, WBSD_IDX_RESP3 + i * 4) << 8; cmd->resp[i] |= wbsd_read_index(host, WBSD_IDX_RESP4 + i * 4) << 0; } } static void wbsd_send_command(struct wbsd_host *host, struct mmc_command *cmd) { int i; u8 status, isr; /* * Clear accumulated ISR. The interrupt routine * will fill this one with events that occur during * transfer. */ host->isr = 0; /* * Send the command (CRC calculated by host). */ outb(cmd->opcode, host->base + WBSD_CMDR); for (i = 3; i >= 0; i--) outb((cmd->arg >> (i * 8)) & 0xff, host->base + WBSD_CMDR); cmd->error = 0; /* * Wait for the request to complete. */ do { status = wbsd_read_index(host, WBSD_IDX_STATUS); } while (status & WBSD_CARDTRAFFIC); /* * Do we expect a reply? */ if (cmd->flags & MMC_RSP_PRESENT) { /* * Read back status. */ isr = host->isr; /* Card removed? */ if (isr & WBSD_INT_CARD) cmd->error = -ENOMEDIUM; /* Timeout? */ else if (isr & WBSD_INT_TIMEOUT) cmd->error = -ETIMEDOUT; /* CRC? */ else if ((cmd->flags & MMC_RSP_CRC) && (isr & WBSD_INT_CRC)) cmd->error = -EILSEQ; /* All ok */ else { if (cmd->flags & MMC_RSP_136) wbsd_get_long_reply(host, cmd); else wbsd_get_short_reply(host, cmd); } } } /* * Data functions */ static void wbsd_empty_fifo(struct wbsd_host *host) { struct mmc_data *data = host->mrq->cmd->data; char *buffer; int i, idx, fsr, fifo; /* * Handle excessive data. */ if (host->num_sg == 0) return; buffer = wbsd_map_sg(host) + host->offset; idx = 0; /* * Drain the fifo. This has a tendency to loop longer * than the FIFO length (usually one block). */ while (!((fsr = inb(host->base + WBSD_FSR)) & WBSD_FIFO_EMPTY)) { /* * The size field in the FSR is broken so we have to * do some guessing. */ if (fsr & WBSD_FIFO_FULL) fifo = 16; else if (fsr & WBSD_FIFO_FUTHRE) fifo = 8; else fifo = 1; for (i = 0; i < fifo; i++) { buffer[idx++] = inb(host->base + WBSD_DFR); host->offset++; host->remain--; data->bytes_xfered++; /* * End of scatter list entry? */ if (host->remain == 0) { kunmap_local(buffer); /* * Get next entry. Check if last. */ if (!wbsd_next_sg(host)) return; buffer = wbsd_map_sg(host); idx = 0; } } } kunmap_local(buffer); /* * This is a very dirty hack to solve a * hardware problem. The chip doesn't trigger * FIFO threshold interrupts properly. */ if ((data->blocks * data->blksz - data->bytes_xfered) < 16) tasklet_schedule(&host->fifo_tasklet); } static void wbsd_fill_fifo(struct wbsd_host *host) { struct mmc_data *data = host->mrq->cmd->data; char *buffer; int i, idx, fsr, fifo; /* * Check that we aren't being called after the * entire buffer has been transferred. */ if (host->num_sg == 0) return; buffer = wbsd_map_sg(host) + host->offset; idx = 0; /* * Fill the fifo. This has a tendency to loop longer * than the FIFO length (usually one block). */ while (!((fsr = inb(host->base + WBSD_FSR)) & WBSD_FIFO_FULL)) { /* * The size field in the FSR is broken so we have to * do some guessing. */ if (fsr & WBSD_FIFO_EMPTY) fifo = 0; else if (fsr & WBSD_FIFO_EMTHRE) fifo = 8; else fifo = 15; for (i = 16; i > fifo; i--) { outb(buffer[idx], host->base + WBSD_DFR); host->offset++; host->remain--; data->bytes_xfered++; /* * End of scatter list entry? */ if (host->remain == 0) { kunmap_local(buffer); /* * Get next entry. Check if last. */ if (!wbsd_next_sg(host)) return; buffer = wbsd_map_sg(host); idx = 0; } } } kunmap_local(buffer); /* * The controller stops sending interrupts for * 'FIFO empty' under certain conditions. So we * need to be a bit more pro-active. */ tasklet_schedule(&host->fifo_tasklet); } static void wbsd_prepare_data(struct wbsd_host *host, struct mmc_data *data) { u16 blksize; u8 setup; unsigned long dmaflags; unsigned int size; /* * Calculate size. */ size = data->blocks * data->blksz; /* * Check timeout values for overflow. * (Yes, some cards cause this value to overflow). */ if (data->timeout_ns > 127000000) wbsd_write_index(host, WBSD_IDX_TAAC, 127); else { wbsd_write_index(host, WBSD_IDX_TAAC, data->timeout_ns / 1000000); } if (data->timeout_clks > 255) wbsd_write_index(host, WBSD_IDX_NSAC, 255); else wbsd_write_index(host, WBSD_IDX_NSAC, data->timeout_clks); /* * Inform the chip of how large blocks will be * sent. It needs this to determine when to * calculate CRC. * * Space for CRC must be included in the size. * Two bytes are needed for each data line. */ if (host->bus_width == MMC_BUS_WIDTH_1) { blksize = data->blksz + 2; wbsd_write_index(host, WBSD_IDX_PBSMSB, (blksize >> 4) & 0xF0); wbsd_write_index(host, WBSD_IDX_PBSLSB, blksize & 0xFF); } else if (host->bus_width == MMC_BUS_WIDTH_4) { blksize = data->blksz + 2 * 4; wbsd_write_index(host, WBSD_IDX_PBSMSB, ((blksize >> 4) & 0xF0) | WBSD_DATA_WIDTH); wbsd_write_index(host, WBSD_IDX_PBSLSB, blksize & 0xFF); } else { data->error = -EINVAL; return; } /* * Clear the FIFO. This is needed even for DMA * transfers since the chip still uses the FIFO * internally. */ setup = wbsd_read_index(host, WBSD_IDX_SETUP); setup |= WBSD_FIFO_RESET; wbsd_write_index(host, WBSD_IDX_SETUP, setup); /* * DMA transfer? */ if (host->dma >= 0) { /* * The buffer for DMA is only 64 kB. */ BUG_ON(size > 0x10000); if (size > 0x10000) { data->error = -EINVAL; return; } /* * Transfer data from the SG list to * the DMA buffer. */ if (data->flags & MMC_DATA_WRITE) wbsd_sg_to_dma(host, data); /* * Initialise the ISA DMA controller. */ dmaflags = claim_dma_lock(); disable_dma(host->dma); clear_dma_ff(host->dma); if (data->flags & MMC_DATA_READ) set_dma_mode(host->dma, DMA_MODE_READ & ~0x40); else set_dma_mode(host->dma, DMA_MODE_WRITE & ~0x40); set_dma_addr(host->dma, host->dma_addr); set_dma_count(host->dma, size); enable_dma(host->dma); release_dma_lock(dmaflags); /* * Enable DMA on the host. */ wbsd_write_index(host, WBSD_IDX_DMA, WBSD_DMA_ENABLE); } else { /* * This flag is used to keep printk * output to a minimum. */ host->firsterr = 1; /* * Initialise the SG list. */ wbsd_init_sg(host, data); /* * Turn off DMA. */ wbsd_write_index(host, WBSD_IDX_DMA, 0); /* * Set up FIFO threshold levels (and fill * buffer if doing a write). */ if (data->flags & MMC_DATA_READ) { wbsd_write_index(host, WBSD_IDX_FIFOEN, WBSD_FIFOEN_FULL | 8); } else { wbsd_write_index(host, WBSD_IDX_FIFOEN, WBSD_FIFOEN_EMPTY | 8); wbsd_fill_fifo(host); } } data->error = 0; } static void wbsd_finish_data(struct wbsd_host *host, struct mmc_data *data) { unsigned long dmaflags; int count; u8 status; WARN_ON(host->mrq == NULL); /* * Send a stop command if needed. */ if (data->stop) wbsd_send_command(host, data->stop); /* * Wait for the controller to leave data * transfer state. */ do { status = wbsd_read_index(host, WBSD_IDX_STATUS); } while (status & (WBSD_BLOCK_READ | WBSD_BLOCK_WRITE)); /* * DMA transfer? */ if (host->dma >= 0) { /* * Disable DMA on the host. */ wbsd_write_index(host, WBSD_IDX_DMA, 0); /* * Turn of ISA DMA controller. */ dmaflags = claim_dma_lock(); disable_dma(host->dma); clear_dma_ff(host->dma); count = get_dma_residue(host->dma); release_dma_lock(dmaflags); data->bytes_xfered = host->mrq->data->blocks * host->mrq->data->blksz - count; data->bytes_xfered -= data->bytes_xfered % data->blksz; /* * Any leftover data? */ if (count) { pr_err("%s: Incomplete DMA transfer. " "%d bytes left.\n", mmc_hostname(host->mmc), count); if (!data->error) data->error = -EIO; } else { /* * Transfer data from DMA buffer to * SG list. */ if (data->flags & MMC_DATA_READ) wbsd_dma_to_sg(host, data); } if (data->error) { if (data->bytes_xfered) data->bytes_xfered -= data->blksz; } } wbsd_request_end(host, host->mrq); } /*****************************************************************************\ * * * MMC layer callbacks * * * \*****************************************************************************/ static void wbsd_request(struct mmc_host *mmc, struct mmc_request *mrq) { struct wbsd_host *host = mmc_priv(mmc); struct mmc_command *cmd; /* * Disable tasklets to avoid a deadlock. */ spin_lock_bh(&host->lock); BUG_ON(host->mrq != NULL); cmd = mrq->cmd; host->mrq = mrq; /* * Check that there is actually a card in the slot. */ if (!(host->flags & WBSD_FCARD_PRESENT)) { cmd->error = -ENOMEDIUM; goto done; } if (cmd->data) { /* * The hardware is so delightfully stupid that it has a list * of "data" commands. If a command isn't on this list, it'll * just go back to the idle state and won't send any data * interrupts. */ switch (cmd->opcode) { case SD_SWITCH_VOLTAGE: case MMC_READ_SINGLE_BLOCK: case MMC_READ_MULTIPLE_BLOCK: case MMC_WRITE_DAT_UNTIL_STOP: case MMC_WRITE_BLOCK: case MMC_WRITE_MULTIPLE_BLOCK: case MMC_PROGRAM_CID: case MMC_PROGRAM_CSD: case MMC_SEND_WRITE_PROT: case MMC_LOCK_UNLOCK: case MMC_GEN_CMD: break; /* ACMDs. We don't keep track of state, so we just treat them * like any other command. */ case SD_APP_SEND_SCR: break; default: pr_warn("%s: Data command %d is not supported by this controller\n", mmc_hostname(host->mmc), cmd->opcode); cmd->error = -EINVAL; goto done; } } /* * Does the request include data? */ if (cmd->data) { wbsd_prepare_data(host, cmd->data); if (cmd->data->error) goto done; } wbsd_send_command(host, cmd); /* * If this is a data transfer the request * will be finished after the data has * transferred. */ if (cmd->data && !cmd->error) { /* * Dirty fix for hardware bug. */ if (host->dma == -1) tasklet_schedule(&host->fifo_tasklet); spin_unlock_bh(&host->lock); return; } done: wbsd_request_end(host, mrq); spin_unlock_bh(&host->lock); } static void wbsd_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) { struct wbsd_host *host = mmc_priv(mmc); u8 clk, setup, pwr; spin_lock_bh(&host->lock); /* * Reset the chip on each power off. * Should clear out any weird states. */ if (ios->power_mode == MMC_POWER_OFF) wbsd_init_device(host); if (ios->clock >= 24000000) clk = WBSD_CLK_24M; else if (ios->clock >= 16000000) clk = WBSD_CLK_16M; else if (ios->clock >= 12000000) clk = WBSD_CLK_12M; else clk = WBSD_CLK_375K; /* * Only write to the clock register when * there is an actual change. */ if (clk != host->clk) { wbsd_write_index(host, WBSD_IDX_CLK, clk); host->clk = clk; } /* * Power up card. */ if (ios->power_mode != MMC_POWER_OFF) { pwr = inb(host->base + WBSD_CSR); pwr &= ~WBSD_POWER_N; outb(pwr, host->base + WBSD_CSR); } /* * MMC cards need to have pin 1 high during init. * It wreaks havoc with the card detection though so * that needs to be disabled. */ setup = wbsd_read_index(host, WBSD_IDX_SETUP); if (ios->chip_select == MMC_CS_HIGH) { BUG_ON(ios->bus_width != MMC_BUS_WIDTH_1); setup |= WBSD_DAT3_H; host->flags |= WBSD_FIGNORE_DETECT; } else { if (setup & WBSD_DAT3_H) { setup &= ~WBSD_DAT3_H; /* * We cannot resume card detection immediately * because of capacitance and delays in the chip. */ mod_timer(&host->ignore_timer, jiffies + HZ / 100); } } wbsd_write_index(host, WBSD_IDX_SETUP, setup); /* * Store bus width for later. Will be used when * setting up the data transfer. */ host->bus_width = ios->bus_width; spin_unlock_bh(&host->lock); } static int wbsd_get_ro(struct mmc_host *mmc) { struct wbsd_host *host = mmc_priv(mmc); u8 csr; spin_lock_bh(&host->lock); csr = inb(host->base + WBSD_CSR); csr |= WBSD_MSLED; outb(csr, host->base + WBSD_CSR); mdelay(1); csr = inb(host->base + WBSD_CSR); csr &= ~WBSD_MSLED; outb(csr, host->base + WBSD_CSR); spin_unlock_bh(&host->lock); return !!(csr & WBSD_WRPT); } static const struct mmc_host_ops wbsd_ops = { .request = wbsd_request, .set_ios = wbsd_set_ios, .get_ro = wbsd_get_ro, }; /*****************************************************************************\ * * * Interrupt handling * * * \*****************************************************************************/ /* * Helper function to reset detection ignore */ static void wbsd_reset_ignore(struct timer_list *t) { struct wbsd_host *host = from_timer(host, t, ignore_timer); BUG_ON(host == NULL); DBG("Resetting card detection ignore\n"); spin_lock_bh(&host->lock); host->flags &= ~WBSD_FIGNORE_DETECT; /* * Card status might have changed during the * blackout. */ tasklet_schedule(&host->card_tasklet); spin_unlock_bh(&host->lock); } /* * Tasklets */ static inline struct mmc_data *wbsd_get_data(struct wbsd_host *host) { WARN_ON(!host->mrq); if (!host->mrq) return NULL; WARN_ON(!host->mrq->cmd); if (!host->mrq->cmd) return NULL; WARN_ON(!host->mrq->cmd->data); if (!host->mrq->cmd->data) return NULL; return host->mrq->cmd->data; } static void wbsd_tasklet_card(struct tasklet_struct *t) { struct wbsd_host *host = from_tasklet(host, t, card_tasklet); u8 csr; int delay = -1; spin_lock(&host->lock); if (host->flags & WBSD_FIGNORE_DETECT) { spin_unlock(&host->lock); return; } csr = inb(host->base + WBSD_CSR); WARN_ON(csr == 0xff); if (csr & WBSD_CARDPRESENT) { if (!(host->flags & WBSD_FCARD_PRESENT)) { DBG("Card inserted\n"); host->flags |= WBSD_FCARD_PRESENT; delay = 500; } } else if (host->flags & WBSD_FCARD_PRESENT) { DBG("Card removed\n"); host->flags &= ~WBSD_FCARD_PRESENT; if (host->mrq) { pr_err("%s: Card removed during transfer!\n", mmc_hostname(host->mmc)); wbsd_reset(host); host->mrq->cmd->error = -ENOMEDIUM; tasklet_schedule(&host->finish_tasklet); } delay = 0; } /* * Unlock first since we might get a call back. */ spin_unlock(&host->lock); if (delay != -1) mmc_detect_change(host->mmc, msecs_to_jiffies(delay)); } static void wbsd_tasklet_fifo(struct tasklet_struct *t) { struct wbsd_host *host = from_tasklet(host, t, fifo_tasklet); struct mmc_data *data; spin_lock(&host->lock); if (!host->mrq) goto end; data = wbsd_get_data(host); if (!data) goto end; if (data->flags & MMC_DATA_WRITE) wbsd_fill_fifo(host); else wbsd_empty_fifo(host); /* * Done? */ if (host->num_sg == 0) { wbsd_write_index(host, WBSD_IDX_FIFOEN, 0); tasklet_schedule(&host->finish_tasklet); } end: spin_unlock(&host->lock); } static void wbsd_tasklet_crc(struct tasklet_struct *t) { struct wbsd_host *host = from_tasklet(host, t, crc_tasklet); struct mmc_data *data; spin_lock(&host->lock); if (!host->mrq) goto end; data = wbsd_get_data(host); if (!data) goto end; DBGF("CRC error\n"); data->error = -EILSEQ; tasklet_schedule(&host->finish_tasklet); end: spin_unlock(&host->lock); } static void wbsd_tasklet_timeout(struct tasklet_struct *t) { struct wbsd_host *host = from_tasklet(host, t, timeout_tasklet); struct mmc_data *data; spin_lock(&host->lock); if (!host->mrq) goto end; data = wbsd_get_data(host); if (!data) goto end; DBGF("Timeout\n"); data->error = -ETIMEDOUT; tasklet_schedule(&host->finish_tasklet); end: spin_unlock(&host->lock); } static void wbsd_tasklet_finish(struct tasklet_struct *t) { struct wbsd_host *host = from_tasklet(host, t, finish_tasklet); struct mmc_data *data; spin_lock(&host->lock); WARN_ON(!host->mrq); if (!host->mrq) goto end; data = wbsd_get_data(host); if (!data) goto end; wbsd_finish_data(host, data); end: spin_unlock(&host->lock); } /* * Interrupt handling */ static irqreturn_t wbsd_irq(int irq, void *dev_id) { struct wbsd_host *host = dev_id; int isr; isr = inb(host->base + WBSD_ISR); /* * Was it actually our hardware that caused the interrupt? */ if (isr == 0xff || isr == 0x00) return IRQ_NONE; host->isr |= isr; /* * Schedule tasklets as needed. */ if (isr & WBSD_INT_CARD) tasklet_schedule(&host->card_tasklet); if (isr & WBSD_INT_FIFO_THRE) tasklet_schedule(&host->fifo_tasklet); if (isr & WBSD_INT_CRC) tasklet_hi_schedule(&host->crc_tasklet); if (isr & WBSD_INT_TIMEOUT) tasklet_hi_schedule(&host->timeout_tasklet); if (isr & WBSD_INT_TC) tasklet_schedule(&host->finish_tasklet); return IRQ_HANDLED; } /*****************************************************************************\ * * * Device initialisation and shutdown * * * \*****************************************************************************/ /* * Allocate/free MMC structure. */ static int wbsd_alloc_mmc(struct device *dev) { struct mmc_host *mmc; struct wbsd_host *host; /* * Allocate MMC structure. */ mmc = mmc_alloc_host(sizeof(struct wbsd_host), dev); if (!mmc) return -ENOMEM; host = mmc_priv(mmc); host->mmc = mmc; host->dma = -1; /* * Set host parameters. */ mmc->ops = &wbsd_ops; mmc->f_min = 375000; mmc->f_max = 24000000; mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; mmc->caps = MMC_CAP_4_BIT_DATA; spin_lock_init(&host->lock); /* * Set up timers */ timer_setup(&host->ignore_timer, wbsd_reset_ignore, 0); /* * Maximum number of segments. Worst case is one sector per segment * so this will be 64kB/512. */ mmc->max_segs = 128; /* * Maximum request size. Also limited by 64KiB buffer. */ mmc->max_req_size = 65536; /* * Maximum segment size. Could be one segment with the maximum number * of bytes. */ mmc->max_seg_size = mmc->max_req_size; /* * Maximum block size. We have 12 bits (= 4095) but have to subtract * space for CRC. So the maximum is 4095 - 4*2 = 4087. */ mmc->max_blk_size = 4087; /* * Maximum block count. There is no real limit so the maximum * request size will be the only restriction. */ mmc->max_blk_count = mmc->max_req_size; dev_set_drvdata(dev, mmc); return 0; } static void wbsd_free_mmc(struct device *dev) { struct mmc_host *mmc; struct wbsd_host *host; mmc = dev_get_drvdata(dev); if (!mmc) return; host = mmc_priv(mmc); BUG_ON(host == NULL); del_timer_sync(&host->ignore_timer); mmc_free_host(mmc); dev_set_drvdata(dev, NULL); } /* * Scan for known chip id:s */ static int wbsd_scan(struct wbsd_host *host) { int i, j, k; int id; /* * Iterate through all ports, all codes to * find hardware that is in our known list. */ for (i = 0; i < ARRAY_SIZE(config_ports); i++) { if (!request_region(config_ports[i], 2, DRIVER_NAME)) continue; for (j = 0; j < ARRAY_SIZE(unlock_codes); j++) { id = 0xFFFF; host->config = config_ports[i]; host->unlock_code = unlock_codes[j]; wbsd_unlock_config(host); outb(WBSD_CONF_ID_HI, config_ports[i]); id = inb(config_ports[i] + 1) << 8; outb(WBSD_CONF_ID_LO, config_ports[i]); id |= inb(config_ports[i] + 1); wbsd_lock_config(host); for (k = 0; k < ARRAY_SIZE(valid_ids); k++) { if (id == valid_ids[k]) { host->chip_id = id; return 0; } } if (id != 0xFFFF) { DBG("Unknown hardware (id %x) found at %x\n", id, config_ports[i]); } } release_region(config_ports[i], 2); } host->config = 0; host->unlock_code = 0; return -ENODEV; } /* * Allocate/free io port ranges */ static int wbsd_request_region(struct wbsd_host *host, int base) { if (base & 0x7) return -EINVAL; if (!request_region(base, 8, DRIVER_NAME)) return -EIO; host->base = base; return 0; } static void wbsd_release_regions(struct wbsd_host *host) { if (host->base) release_region(host->base, 8); host->base = 0; if (host->config) release_region(host->config, 2); host->config = 0; } /* * Allocate/free DMA port and buffer */ static void wbsd_request_dma(struct wbsd_host *host, int dma) { if (dma < 0) return; if (request_dma(dma, DRIVER_NAME)) goto err; /* * We need to allocate a special buffer in * order for ISA to be able to DMA to it. */ host->dma_buffer = kmalloc(WBSD_DMA_SIZE, GFP_NOIO | GFP_DMA | __GFP_RETRY_MAYFAIL | __GFP_NOWARN); if (!host->dma_buffer) goto free; /* * Translate the address to a physical address. */ host->dma_addr = dma_map_single(mmc_dev(host->mmc), host->dma_buffer, WBSD_DMA_SIZE, DMA_BIDIRECTIONAL); if (dma_mapping_error(mmc_dev(host->mmc), host->dma_addr)) goto kfree; /* * ISA DMA must be aligned on a 64k basis. */ if ((host->dma_addr & 0xffff) != 0) goto unmap; /* * ISA cannot access memory above 16 MB. */ else if (host->dma_addr >= 0x1000000) goto unmap; host->dma = dma; return; unmap: /* * If we've gotten here then there is some kind of alignment bug */ BUG_ON(1); dma_unmap_single(mmc_dev(host->mmc), host->dma_addr, WBSD_DMA_SIZE, DMA_BIDIRECTIONAL); host->dma_addr = 0; kfree: kfree(host->dma_buffer); host->dma_buffer = NULL; free: free_dma(dma); err: pr_warn(DRIVER_NAME ": Unable to allocate DMA %d - falling back on FIFO\n", dma); } static void wbsd_release_dma(struct wbsd_host *host) { /* * host->dma_addr is valid here iff host->dma_buffer is not NULL. */ if (host->dma_buffer) { dma_unmap_single(mmc_dev(host->mmc), host->dma_addr, WBSD_DMA_SIZE, DMA_BIDIRECTIONAL); kfree(host->dma_buffer); } if (host->dma >= 0) free_dma(host->dma); host->dma = -1; host->dma_buffer = NULL; host->dma_addr = 0; } /* * Allocate/free IRQ. */ static int wbsd_request_irq(struct wbsd_host *host, int irq) { int ret; /* * Set up tasklets. Must be done before requesting interrupt. */ tasklet_setup(&host->card_tasklet, wbsd_tasklet_card); tasklet_setup(&host->fifo_tasklet, wbsd_tasklet_fifo); tasklet_setup(&host->crc_tasklet, wbsd_tasklet_crc); tasklet_setup(&host->timeout_tasklet, wbsd_tasklet_timeout); tasklet_setup(&host->finish_tasklet, wbsd_tasklet_finish); /* * Allocate interrupt. */ ret = request_irq(irq, wbsd_irq, IRQF_SHARED, DRIVER_NAME, host); if (ret) return ret; host->irq = irq; return 0; } static void wbsd_release_irq(struct wbsd_host *host) { if (!host->irq) return; free_irq(host->irq, host); host->irq = 0; tasklet_kill(&host->card_tasklet); tasklet_kill(&host->fifo_tasklet); tasklet_kill(&host->crc_tasklet); tasklet_kill(&host->timeout_tasklet); tasklet_kill(&host->finish_tasklet); } /* * Allocate all resources for the host. */ static int wbsd_request_resources(struct wbsd_host *host, int base, int irq, int dma) { int ret; /* * Allocate I/O ports. */ ret = wbsd_request_region(host, base); if (ret) return ret; /* * Allocate interrupt. */ ret = wbsd_request_irq(host, irq); if (ret) return ret; /* * Allocate DMA. */ wbsd_request_dma(host, dma); return 0; } /* * Release all resources for the host. */ static void wbsd_release_resources(struct wbsd_host *host) { wbsd_release_dma(host); wbsd_release_irq(host); wbsd_release_regions(host); } /* * Configure the resources the chip should use. */ static void wbsd_chip_config(struct wbsd_host *host) { wbsd_unlock_config(host); /* * Reset the chip. */ wbsd_write_config(host, WBSD_CONF_SWRST, 1); wbsd_write_config(host, WBSD_CONF_SWRST, 0); /* * Select SD/MMC function. */ wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD); /* * Set up card detection. */ wbsd_write_config(host, WBSD_CONF_PINS, WBSD_PINS_DETECT_GP11); /* * Configure chip */ wbsd_write_config(host, WBSD_CONF_PORT_HI, host->base >> 8); wbsd_write_config(host, WBSD_CONF_PORT_LO, host->base & 0xff); wbsd_write_config(host, WBSD_CONF_IRQ, host->irq); if (host->dma >= 0) wbsd_write_config(host, WBSD_CONF_DRQ, host->dma); /* * Enable and power up chip. */ wbsd_write_config(host, WBSD_CONF_ENABLE, 1); wbsd_write_config(host, WBSD_CONF_POWER, 0x20); wbsd_lock_config(host); } /* * Check that configured resources are correct. */ static int wbsd_chip_validate(struct wbsd_host *host) { int base, irq, dma; wbsd_unlock_config(host); /* * Select SD/MMC function. */ wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD); /* * Read configuration. */ base = wbsd_read_config(host, WBSD_CONF_PORT_HI) << 8; base |= wbsd_read_config(host, WBSD_CONF_PORT_LO); irq = wbsd_read_config(host, WBSD_CONF_IRQ); dma = wbsd_read_config(host, WBSD_CONF_DRQ); wbsd_lock_config(host); /* * Validate against given configuration. */ if (base != host->base) return 0; if (irq != host->irq) return 0; if ((dma != host->dma) && (host->dma != -1)) return 0; return 1; } /* * Powers down the SD function */ static void wbsd_chip_poweroff(struct wbsd_host *host) { wbsd_unlock_config(host); wbsd_write_config(host, WBSD_CONF_DEVICE, DEVICE_SD); wbsd_write_config(host, WBSD_CONF_ENABLE, 0); wbsd_lock_config(host); } /*****************************************************************************\ * * * Devices setup and shutdown * * * \*****************************************************************************/ static int wbsd_init(struct device *dev, int base, int irq, int dma, int pnp) { struct wbsd_host *host = NULL; struct mmc_host *mmc = NULL; int ret; ret = wbsd_alloc_mmc(dev); if (ret) return ret; mmc = dev_get_drvdata(dev); host = mmc_priv(mmc); /* * Scan for hardware. */ ret = wbsd_scan(host); if (ret) { if (pnp && (ret == -ENODEV)) { pr_warn(DRIVER_NAME ": Unable to confirm device presence - you may experience lock-ups\n"); } else { wbsd_free_mmc(dev); return ret; } } /* * Request resources. */ ret = wbsd_request_resources(host, base, irq, dma); if (ret) { wbsd_release_resources(host); wbsd_free_mmc(dev); return ret; } /* * See if chip needs to be configured. */ if (pnp) { if ((host->config != 0) && !wbsd_chip_validate(host)) { pr_warn(DRIVER_NAME ": PnP active but chip not configured! You probably have a buggy BIOS. Configuring chip manually.\n"); wbsd_chip_config(host); } } else wbsd_chip_config(host); /* * Power Management stuff. No idea how this works. * Not tested. */ #ifdef CONFIG_PM if (host->config) { wbsd_unlock_config(host); wbsd_write_config(host, WBSD_CONF_PME, 0xA0); wbsd_lock_config(host); } #endif /* * Allow device to initialise itself properly. */ mdelay(5); /* * Reset the chip into a known state. */ wbsd_init_device(host); ret = mmc_add_host(mmc); if (ret) { if (!pnp) wbsd_chip_poweroff(host); wbsd_release_resources(host); wbsd_free_mmc(dev); return ret; } pr_info("%s: W83L51xD", mmc_hostname(mmc)); if (host->chip_id != 0) printk(" id %x", (int)host->chip_id); printk(" at 0x%x irq %d", (int)host->base, (int)host->irq); if (host->dma >= 0) printk(" dma %d", (int)host->dma); else printk(" FIFO"); if (pnp) printk(" PnP"); printk("\n"); return 0; } static void wbsd_shutdown(struct device *dev, int pnp) { struct mmc_host *mmc = dev_get_drvdata(dev); struct wbsd_host *host; if (!mmc) return; host = mmc_priv(mmc); mmc_remove_host(mmc); /* * Power down the SD/MMC function. */ if (!pnp) wbsd_chip_poweroff(host); wbsd_release_resources(host); wbsd_free_mmc(dev); } /* * Non-PnP */ static int wbsd_probe(struct platform_device *dev) { /* Use the module parameters for resources */ return wbsd_init(&dev->dev, param_io, param_irq, param_dma, 0); } static int wbsd_remove(struct platform_device *dev) { wbsd_shutdown(&dev->dev, 0); return 0; } /* * PnP */ #ifdef CONFIG_PNP static int wbsd_pnp_probe(struct pnp_dev *pnpdev, const struct pnp_device_id *dev_id) { int io, irq, dma; /* * Get resources from PnP layer. */ io = pnp_port_start(pnpdev, 0); irq = pnp_irq(pnpdev, 0); if (pnp_dma_valid(pnpdev, 0)) dma = pnp_dma(pnpdev, 0); else dma = -1; DBGF("PnP resources: port %3x irq %d dma %d\n", io, irq, dma); return wbsd_init(&pnpdev->dev, io, irq, dma, 1); } static void wbsd_pnp_remove(struct pnp_dev *dev) { wbsd_shutdown(&dev->dev, 1); } #endif /* CONFIG_PNP */ /* * Power management */ #ifdef CONFIG_PM static int wbsd_platform_suspend(struct platform_device *dev, pm_message_t state) { struct mmc_host *mmc = platform_get_drvdata(dev); struct wbsd_host *host; if (mmc == NULL) return 0; DBGF("Suspending...\n"); host = mmc_priv(mmc); wbsd_chip_poweroff(host); return 0; } static int wbsd_platform_resume(struct platform_device *dev) { struct mmc_host *mmc = platform_get_drvdata(dev); struct wbsd_host *host; if (mmc == NULL) return 0; DBGF("Resuming...\n"); host = mmc_priv(mmc); wbsd_chip_config(host); /* * Allow device to initialise itself properly. */ mdelay(5); wbsd_init_device(host); return 0; } #ifdef CONFIG_PNP static int wbsd_pnp_suspend(struct pnp_dev *pnp_dev, pm_message_t state) { struct mmc_host *mmc = dev_get_drvdata(&pnp_dev->dev); if (mmc == NULL) return 0; DBGF("Suspending...\n"); return 0; } static int wbsd_pnp_resume(struct pnp_dev *pnp_dev) { struct mmc_host *mmc = dev_get_drvdata(&pnp_dev->dev); struct wbsd_host *host; if (mmc == NULL) return 0; DBGF("Resuming...\n"); host = mmc_priv(mmc); /* * See if chip needs to be configured. */ if (host->config != 0) { if (!wbsd_chip_validate(host)) { pr_warn(DRIVER_NAME ": PnP active but chip not configured! You probably have a buggy BIOS. Configuring chip manually.\n"); wbsd_chip_config(host); } } /* * Allow device to initialise itself properly. */ mdelay(5); wbsd_init_device(host); return 0; } #endif /* CONFIG_PNP */ #else /* CONFIG_PM */ #define wbsd_platform_suspend NULL #define wbsd_platform_resume NULL #define wbsd_pnp_suspend NULL #define wbsd_pnp_resume NULL #endif /* CONFIG_PM */ static struct platform_device *wbsd_device; static struct platform_driver wbsd_driver = { .probe = wbsd_probe, .remove = wbsd_remove, .suspend = wbsd_platform_suspend, .resume = wbsd_platform_resume, .driver = { .name = DRIVER_NAME, .probe_type = PROBE_PREFER_ASYNCHRONOUS, }, }; #ifdef CONFIG_PNP static struct pnp_driver wbsd_pnp_driver = { .name = DRIVER_NAME, .id_table = pnp_dev_table, .probe = wbsd_pnp_probe, .remove = wbsd_pnp_remove, .suspend = wbsd_pnp_suspend, .resume = wbsd_pnp_resume, }; #endif /* CONFIG_PNP */ /* * Module loading/unloading */ static int __init wbsd_drv_init(void) { int result; pr_info(DRIVER_NAME ": Winbond W83L51xD SD/MMC card interface driver\n"); pr_info(DRIVER_NAME ": Copyright(c) Pierre Ossman\n"); #ifdef CONFIG_PNP if (!param_nopnp) { result = pnp_register_driver(&wbsd_pnp_driver); if (result < 0) return result; } #endif /* CONFIG_PNP */ if (param_nopnp) { result = platform_driver_register(&wbsd_driver); if (result < 0) return result; wbsd_device = platform_device_alloc(DRIVER_NAME, -1); if (!wbsd_device) { platform_driver_unregister(&wbsd_driver); return -ENOMEM; } result = platform_device_add(wbsd_device); if (result) { platform_device_put(wbsd_device); platform_driver_unregister(&wbsd_driver); return result; } } return 0; } static void __exit wbsd_drv_exit(void) { #ifdef CONFIG_PNP if (!param_nopnp) pnp_unregister_driver(&wbsd_pnp_driver); #endif /* CONFIG_PNP */ if (param_nopnp) { platform_device_unregister(wbsd_device); platform_driver_unregister(&wbsd_driver); } DBG("unloaded\n"); } module_init(wbsd_drv_init); module_exit(wbsd_drv_exit); #ifdef CONFIG_PNP module_param_hw_named(nopnp, param_nopnp, uint, other, 0444); #endif module_param_hw_named(io, param_io, uint, ioport, 0444); module_param_hw_named(irq, param_irq, uint, irq, 0444); module_param_hw_named(dma, param_dma, int, dma, 0444); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Pierre Ossman <pierre@ossman.eu>"); MODULE_DESCRIPTION("Winbond W83L51xD SD/MMC card interface driver"); #ifdef CONFIG_PNP MODULE_PARM_DESC(nopnp, "Scan for device instead of relying on PNP. (default 0)"); #endif MODULE_PARM_DESC(io, "I/O base to allocate. Must be 8 byte aligned. (default 0x248)"); MODULE_PARM_DESC(irq, "IRQ to allocate. (default 6)"); MODULE_PARM_DESC(dma, "DMA channel to allocate. -1 for no DMA. (default 2)");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
You can’t perform that action at this time.