diff options
author | Quinn Jensen <quinn.jensen@freescale.com> | 2007-10-24 21:22:22 -0600 |
---|---|---|
committer | Quinn Jensen <quinn.jensen@freescale.com> | 2007-10-24 21:22:22 -0600 |
commit | c70c242dc8726a3e48805dda08624251f41353ad (patch) | |
tree | fb127b6399d3fcf9ef53253129aba7def81090d2 /drivers/mmc | |
parent | 7eeec8642b84086fcff550f1d1d8f24a55ad5fc3 (diff) |
Community MMC/SD/SDIO support from Pierre Ossman's tree
Patch to add Community MMC/SD/SDIO support from Pierre Ossman's tree
git://git.kernel.org/pub/scm/linux/kernel/git/drzeus/mmc.git
Author: Pierre Ossman
Snapshot taken 20070910, and applied to linux 2.6.22.6 by Quinn Jensen.
Portions Copyright Pierre Ossman and Monta Vista.
http://www.bitshrine.org/gpp/linux-2.6.22-mx-Community-MMC-SD-SDIO-support-from-Pierre-.patch
Diffstat (limited to 'drivers/mmc')
30 files changed, 3625 insertions, 258 deletions
diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig index a49cb9737cd8..aa8a4e461942 100644 --- a/drivers/mmc/card/Kconfig +++ b/drivers/mmc/card/Kconfig @@ -32,3 +32,10 @@ config MMC_BLOCK_BOUNCE If unsure, say Y here. +config SDIO_UART + tristate "SDIO UART/GPS class support" + depends on MMC + help + SDIO function driver for SDIO cards that implements the UART + class, as well as the GPS class which appears like a UART. + diff --git a/drivers/mmc/card/Makefile b/drivers/mmc/card/Makefile index cf8c939867f5..fc5a784cfa1a 100644 --- a/drivers/mmc/card/Makefile +++ b/drivers/mmc/card/Makefile @@ -9,3 +9,5 @@ endif obj-$(CONFIG_MMC_BLOCK) += mmc_block.o mmc_block-objs := block.o queue.o +obj-$(CONFIG_SDIO_UART) += sdio_uart.o + diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 93fe2e5dd616..0da341acf32b 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -154,7 +154,7 @@ static u32 mmc_sd_num_wr_blocks(struct mmc_card *card) cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; err = mmc_wait_for_cmd(card->host, &cmd, 0); - if ((err != MMC_ERR_NONE) || !(cmd.resp[0] & R1_APP_CMD)) + if (err || !(cmd.resp[0] & R1_APP_CMD)) return (u32)-1; memset(&cmd, 0, sizeof(struct mmc_command)); @@ -192,7 +192,7 @@ static u32 mmc_sd_num_wr_blocks(struct mmc_card *card) mmc_wait_for_req(card->host, &mrq); - if (cmd.error != MMC_ERR_NONE || data.error != MMC_ERR_NONE) + if (cmd.error || data.error) return (u32)-1; blocks = ntohl(blocks); diff --git a/drivers/mmc/card/sdio_uart.c b/drivers/mmc/card/sdio_uart.c new file mode 100644 index 000000000000..d552de683110 --- /dev/null +++ b/drivers/mmc/card/sdio_uart.c @@ -0,0 +1,1158 @@ +/* + * linux/drivers/mmc/card/sdio_uart.c - SDIO UART/GPS driver + * + * Based on drivers/serial/8250.c and drivers/serial/serial_core.c + * by Russell King. + * + * Author: Nicolas Pitre + * Created: June 15, 2007 + * Copyright: MontaVista Software, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +/* + * Note: Although this driver assumes a 16550A-like UART implementation, + * it is not possible to leverage the common 8250/16550 driver, nor the + * core UART infrastructure, as they assumes direct access to the hardware + * registers, often under a spinlock. This is not possible in the SDIO + * context as SDIO access functions must be able to sleep. + * + * Because we need to lock the SDIO host to ensure an exclusive access to + * the card, we simply rely on that lock to also prevent and serialize + * concurrent access to the same port. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/mutex.h> +#include <linux/serial_reg.h> +#include <linux/circ_buf.h> +#include <linux/gfp.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> + +#include <linux/mmc/core.h> +#include <linux/mmc/card.h> +#include <linux/mmc/sdio_func.h> +#include <linux/mmc/sdio_ids.h> + + +#define UART_NR 8 /* Number of UARTs this driver can handle */ + + +#define UART_XMIT_SIZE PAGE_SIZE +#define WAKEUP_CHARS 256 + +#define circ_empty(circ) ((circ)->head == (circ)->tail) +#define circ_clear(circ) ((circ)->head = (circ)->tail = 0) + +#define circ_chars_pending(circ) \ + (CIRC_CNT((circ)->head, (circ)->tail, UART_XMIT_SIZE)) + +#define circ_chars_free(circ) \ + (CIRC_SPACE((circ)->head, (circ)->tail, UART_XMIT_SIZE)) + + +struct uart_icount { + __u32 cts; + __u32 dsr; + __u32 rng; + __u32 dcd; + __u32 rx; + __u32 tx; + __u32 frame; + __u32 overrun; + __u32 parity; + __u32 brk; +}; + +struct sdio_uart_port { + struct kref kref; + struct tty_struct *tty; + unsigned int index; + unsigned int opened; + struct mutex open_lock; + struct sdio_func *func; + struct mutex func_lock; + struct task_struct *in_sdio_uart_irq; + unsigned int regs_offset; + struct circ_buf xmit; + spinlock_t write_lock; + struct uart_icount icount; + unsigned int uartclk; + unsigned int mctrl; + unsigned int read_status_mask; + unsigned int ignore_status_mask; + unsigned char x_char; + unsigned char ier; + unsigned char lcr; +}; + +static struct sdio_uart_port *sdio_uart_table[UART_NR]; +static DEFINE_SPINLOCK(sdio_uart_table_lock); + +static int sdio_uart_add_port(struct sdio_uart_port *port) +{ + int index, ret = -EBUSY; + + kref_init(&port->kref); + mutex_init(&port->open_lock); + mutex_init(&port->func_lock); + spin_lock_init(&port->write_lock); + + spin_lock(&sdio_uart_table_lock); + for (index = 0; index < UART_NR; index++) { + if (!sdio_uart_table[index]) { + port->index = index; + sdio_uart_table[index] = port; + ret = 0; + break; + } + } + spin_unlock(&sdio_uart_table_lock); + + return ret; +} + +static struct sdio_uart_port *sdio_uart_port_get(unsigned index) +{ + struct sdio_uart_port *port; + + if (index >= UART_NR) + return NULL; + + spin_lock(&sdio_uart_table_lock); + port = sdio_uart_table[index]; + if (port) + kref_get(&port->kref); + spin_unlock(&sdio_uart_table_lock); + + return port; +} + +static void sdio_uart_port_destroy(struct kref *kref) +{ + struct sdio_uart_port *port = + container_of(kref, struct sdio_uart_port, kref); + kfree(port); +} + +static void sdio_uart_port_put(struct sdio_uart_port *port) +{ + kref_put(&port->kref, sdio_uart_port_destroy); +} + +static void sdio_uart_port_remove(struct sdio_uart_port *port) +{ + struct sdio_func *func; + + BUG_ON(sdio_uart_table[port->index] != port); + + spin_lock(&sdio_uart_table_lock); + sdio_uart_table[port->index] = NULL; + spin_unlock(&sdio_uart_table_lock); + + /* + * We're killing a port that potentially still is in use by + * the tty layer. Be careful to prevent any further access + * to the SDIO function and arrange for the tty layer to + * give up on that port ASAP. + * Beware: the lock ordering is critical. + */ + mutex_lock(&port->open_lock); + mutex_lock(&port->func_lock); + func = port->func; + sdio_claim_host(func); + port->func = NULL; + mutex_unlock(&port->func_lock); + if (port->opened) + tty_hangup(port->tty); + mutex_unlock(&port->open_lock); + sdio_release_irq(func); + sdio_disable_func(func); + sdio_release_host(func); + + sdio_uart_port_put(port); +} + +static int sdio_uart_claim_func(struct sdio_uart_port *port) +{ + mutex_lock(&port->func_lock); + if (unlikely(!port->func)) { + mutex_unlock(&port->func_lock); + return -ENODEV; + } + if (likely(port->in_sdio_uart_irq != current)) + sdio_claim_host(port->func); + mutex_unlock(&port->func_lock); + return 0; +} + +static inline void sdio_uart_release_func(struct sdio_uart_port *port) +{ + if (likely(port->in_sdio_uart_irq != current)) + sdio_release_host(port->func); +} + +static inline unsigned int sdio_in(struct sdio_uart_port *port, int offset) +{ + unsigned char c; + c = sdio_readb(port->func, port->regs_offset + offset, NULL); + return c; +} + +static inline void sdio_out(struct sdio_uart_port *port, int offset, int value) +{ + sdio_writeb(port->func, value, port->regs_offset + offset, NULL); +} + +static unsigned int sdio_uart_get_mctrl(struct sdio_uart_port *port) +{ + unsigned char status; + unsigned int ret; + + status = sdio_in(port, UART_MSR); + + ret = 0; + if (status & UART_MSR_DCD) + ret |= TIOCM_CAR; + if (status & UART_MSR_RI) + ret |= TIOCM_RNG; + if (status & UART_MSR_DSR) + ret |= TIOCM_DSR; + if (status & UART_MSR_CTS) + ret |= TIOCM_CTS; + return ret; +} + +static void sdio_uart_write_mctrl(struct sdio_uart_port *port, unsigned int mctrl) +{ + unsigned char mcr = 0; + + if (mctrl & TIOCM_RTS) + mcr |= UART_MCR_RTS; + if (mctrl & TIOCM_DTR) + mcr |= UART_MCR_DTR; + if (mctrl & TIOCM_OUT1) + mcr |= UART_MCR_OUT1; + if (mctrl & TIOCM_OUT2) + mcr |= UART_MCR_OUT2; + if (mctrl & TIOCM_LOOP) + mcr |= UART_MCR_LOOP; + + sdio_out(port, UART_MCR, mcr); +} + +static inline void sdio_uart_update_mctrl(struct sdio_uart_port *port, + unsigned int set, unsigned int clear) +{ + unsigned int old; + + old = port->mctrl; + port->mctrl = (old & ~clear) | set; + if (old != port->mctrl) + sdio_uart_write_mctrl(port, port->mctrl); +} + +#define sdio_uart_set_mctrl(port, x) sdio_uart_update_mctrl(port, x, 0) +#define sdio_uart_clear_mctrl(port, x) sdio_uart_update_mctrl(port, 0, x) + +static void sdio_uart_change_speed(struct sdio_uart_port *port, + struct ktermios *termios, + struct ktermios *old) +{ + unsigned char cval, fcr = 0; + unsigned int baud, quot; + + switch (termios->c_cflag & CSIZE) { + case CS5: + cval = UART_LCR_WLEN5; + break; + case CS6: + cval = UART_LCR_WLEN6; + break; + case CS7: + cval = UART_LCR_WLEN7; + break; + default: + case CS8: + cval = UART_LCR_WLEN8; + break; + } + + if (termios->c_cflag & CSTOPB) + cval |= UART_LCR_STOP; + if (termios->c_cflag & PARENB) + cval |= UART_LCR_PARITY; + if (!(termios->c_cflag & PARODD)) + cval |= UART_LCR_EPAR; + + for (;;) { + baud = tty_termios_baud_rate(termios); + if (baud == 0) + baud = 9600; /* Special case: B0 rate. */ + if (baud <= port->uartclk) + break; + /* + * Oops, the quotient was zero. Try again with the old + * baud rate if possible, otherwise default to 9600. + */ + termios->c_cflag &= ~CBAUD; + if (old) { + termios->c_cflag |= old->c_cflag & CBAUD; + old = NULL; + } else + termios->c_cflag |= B9600; + } + quot = (2 * port->uartclk + baud) / (2 * baud); + + if (baud < 2400) + fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_1; + else + fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_10; + + port->read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR; + if (termios->c_iflag & INPCK) + port->read_status_mask |= UART_LSR_FE | UART_LSR_PE; + if (termios->c_iflag & (BRKINT | PARMRK)) + port->read_status_mask |= UART_LSR_BI; + + /* + * Characters to ignore + */ + port->ignore_status_mask = 0; + if (termios->c_iflag & IGNPAR) + port->ignore_status_mask |= UART_LSR_PE | UART_LSR_FE; + if (termios->c_iflag & IGNBRK) { + port->ignore_status_mask |= UART_LSR_BI; + /* + * If we're ignoring parity and break indicators, + * ignore overruns too (for real raw support). + */ + if (termios->c_iflag & IGNPAR) + port->ignore_status_mask |= UART_LSR_OE; + } + + /* + * ignore all characters if CREAD is not set + */ + if ((termios->c_cflag & CREAD) == 0) + port->ignore_status_mask |= UART_LSR_DR; + + /* + * CTS flow control flag and modem status interrupts + */ + port->ier &= ~UART_IER_MSI; + if ((termios->c_cflag & CRTSCTS) || !(termios->c_cflag & CLOCAL)) + port->ier |= UART_IER_MSI; + + port->lcr = cval; + + sdio_out(port, UART_IER, port->ier); + sdio_out(port, UART_LCR, cval | UART_LCR_DLAB); + sdio_out(port, UART_DLL, quot & 0xff); + sdio_out(port, UART_DLM, quot >> 8); + sdio_out(port, UART_LCR, cval); + sdio_out(port, UART_FCR, fcr); + + sdio_uart_write_mctrl(port, port->mctrl); +} + +static void sdio_uart_start_tx(struct sdio_uart_port *port) +{ + if (!(port->ier & UART_IER_THRI)) { + port->ier |= UART_IER_THRI; + sdio_out(port, UART_IER, port->ier); + } +} + +static void sdio_uart_stop_tx(struct sdio_uart_port *port) +{ + if (port->ier & UART_IER_THRI) { + port->ier &= ~UART_IER_THRI; + sdio_out(port, UART_IER, port->ier); + } +} + +static void sdio_uart_stop_rx(struct sdio_uart_port *port) +{ + port->ier &= ~UART_IER_RLSI; + port->read_status_mask &= ~UART_LSR_DR; + sdio_out(port, UART_IER, port->ier); +} + +static void sdio_uart_receive_chars(struct sdio_uart_port *port, int *status) +{ + struct tty_struct *tty = port->tty; + unsigned int ch, flag; + int max_count = 256; + + do { + ch = sdio_in(port, UART_RX); + flag = TTY_NORMAL; + port->icount.rx++; + + if (unlikely(*status & (UART_LSR_BI | UART_LSR_PE | + UART_LSR_FE | UART_LSR_OE))) { + /* + * For statistics only + */ + if (*status & UART_LSR_BI) { + *status &= ~(UART_LSR_FE | UART_LSR_PE); + port->icount.brk++; + } else if (*status & UART_LSR_PE) + port->icount.parity++; + else if (*status & UART_LSR_FE) + port->icount.frame++; + if (*status & UART_LSR_OE) + port->icount.overrun++; + + /* + * Mask off conditions which should be ignored. + */ + *status &= port->read_status_mask; + if (*status & UART_LSR_BI) { + flag = TTY_BREAK; + } else if (*status & UART_LSR_PE) + flag = TTY_PARITY; + else if (*status & UART_LSR_FE) + flag = TTY_FRAME; + } + + if ((*status & port->ignore_status_mask & ~UART_LSR_OE) == 0) + tty_insert_flip_char(tty, ch, flag); + + /* + * Overrun is special. Since it's reported immediately, + * it doesn't affect the current character. + */ + if (*status & ~port->ignore_status_mask & UART_LSR_OE) + tty_insert_flip_char(tty, 0, TTY_OVERRUN); + + *status = sdio_in(port, UART_LSR); + } while ((*status & UART_LSR_DR) && (max_count-- > 0)); + tty_flip_buffer_push(tty); +} + +static void sdio_uart_transmit_chars(struct sdio_uart_port *port) +{ + struct circ_buf *xmit = &port->xmit; + int count; + + if (port->x_char) { + sdio_out(port, UART_TX, port->x_char); + port->icount.tx++; + port->x_char = 0; + return; + } + if (circ_empty(xmit) || port->tty->stopped || port->tty->hw_stopped) { + sdio_uart_stop_tx(port); + return; + } + + count = 16; + do { + sdio_out(port, UART_TX, xmit->buf[xmit->tail]); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + port->icount.tx++; + if (circ_empty(xmit)) + break; + } while (--count > 0); + + if (circ_chars_pending(xmit) < WAKEUP_CHARS) + tty_wakeup(port->tty); + + if (circ_empty(xmit)) + sdio_uart_stop_tx(port); +} + +static void sdio_uart_check_modem_status(struct sdio_uart_port *port) +{ + int status; + + status = sdio_in(port, UART_MSR); + + if ((status & UART_MSR_ANY_DELTA) == 0) + return; + + if (status & UART_MSR_TERI) + port->icount.rng++; + if (status & UART_MSR_DDSR) + port->icount.dsr++; + if (status & UART_MSR_DDCD) + port->icount.dcd++; + if (status & UART_MSR_DCTS) { + port->icount.cts++; + if (port->tty->termios->c_cflag & CRTSCTS) { + int cts = (status & UART_MSR_CTS); + if (port->tty->hw_stopped) { + if (cts) { + port->tty->hw_stopped = 0; + sdio_uart_start_tx(port); + tty_wakeup(port->tty); + } + } else { + if (!cts) { + port->tty->hw_stopped = 1; + sdio_uart_stop_tx(port); + } + } + } + } +} + +/* + * This handles the interrupt from one port. + */ +static void sdio_uart_irq(struct sdio_func *func) +{ + struct sdio_uart_port *port = sdio_get_drvdata(func); + unsigned int iir, lsr; + + /* + * In a few places sdio_uart_irq() is called directly instead of + * waiting for the actual interrupt to be raised and the SDIO IRQ + * thread scheduled in order to reduce latency. However, some + * interaction with the tty core may end up calling us back + * (serial echo, flow control, etc.) through those same places + * causing undesirable effects. Let's stop the recursion here. + */ + if (unlikely(port->in_sdio_uart_irq == current)) + return; + + iir = sdio_in(port, UART_IIR); + if (iir & UART_IIR_NO_INT) + return; + + port->in_sdio_uart_irq = current; + lsr = sdio_in(port, UART_LSR); + if (lsr & UART_LSR_DR) + sdio_uart_receive_chars(port, &lsr); + sdio_uart_check_modem_status(port); + if (lsr & UART_LSR_THRE) + sdio_uart_transmit_chars(port); + port->in_sdio_uart_irq = NULL; +} + +static int sdio_uart_startup(struct sdio_uart_port *port) +{ + unsigned long page; + int ret; + + /* + * Set the TTY IO error marker - we will only clear this + * once we have successfully opened the port. + */ + set_bit(TTY_IO_ERROR, &port->tty->flags); + + /* Initialise and allocate the transmit buffer. */ + page = __get_free_page(GFP_KERNEL); + if (!page) + return -ENOMEM; + port->xmit.buf = (unsigned char *)page; + circ_clear(&port->xmit); + + ret = sdio_uart_claim_func(port); + if (ret) + goto err1; + ret = sdio_enable_func(port->func); + if (ret) + goto err2; + ret = sdio_claim_irq(port->func, sdio_uart_irq); + if (ret) + goto err3; + + /* + * Clear the FIFO buffers and disable them. + * (they will be reenabled in sdio_change_speed()) + */ + sdio_out(port, UART_FCR, UART_FCR_ENABLE_FIFO); + sdio_out(port, UART_FCR, UART_FCR_ENABLE_FIFO | + UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT); + sdio_out(port, UART_FCR, 0); + + /* + * Clear the interrupt registers. + */ + (void) sdio_in(port, UART_LSR); + (void) sdio_in(port, UART_RX); + (void) sdio_in(port, UART_IIR); + (void) sdio_in(port, UART_MSR); + + /* + * Now, initialize the UART + */ + sdio_out(port, UART_LCR, UART_LCR_WLEN8); + + port->ier = UART_IER_RLSI | UART_IER_RDI | UART_IER_RTOIE | UART_IER_UUE; + port->mctrl = TIOCM_OUT2; + + sdio_uart_change_speed(port, port->tty->termios, NULL); + + if (port->tty->termios->c_cflag & CBAUD) + sdio_uart_set_mctrl(port, TIOCM_RTS | TIOCM_DTR); + + if (port->tty->termios->c_cflag & CRTSCTS) + if (!(sdio_uart_get_mctrl(port) & TIOCM_CTS)) + port->tty->hw_stopped = 1; + + clear_bit(TTY_IO_ERROR, &port->tty->flags); + + /* Kick the IRQ handler once while we're still holding the host lock */ + sdio_uart_irq(port->func); + + sdio_uart_release_func(port); + return 0; + +err3: + sdio_disable_func(port->func); +err2: + sdio_uart_release_func(port); +err1: + free_page((unsigned long)port->xmit.buf); + return ret; +} + +static void sdio_uart_shutdown(struct sdio_uart_port *port) +{ + int ret; + + ret = sdio_uart_claim_func(port); + if (ret) + goto skip; + + sdio_uart_stop_rx(port); + + /* TODO: wait here for TX FIFO to drain */ + + /* Turn off DTR and RTS early. */ + if (port->tty->termios->c_cflag & HUPCL) + sdio_uart_clear_mctrl(port, TIOCM_DTR | TIOCM_RTS); + + /* Disable interrupts from this port */ + sdio_release_irq(port->func); + port->ier = 0; + sdio_out(port, UART_IER, 0); + + sdio_uart_clear_mctrl(port, TIOCM_OUT2); + + /* Disable break condition and FIFOs. */ + port->lcr &= ~UART_LCR_SBC; + sdio_out(port, UART_LCR, port->lcr); + sdio_out(port, UART_FCR, UART_FCR_ENABLE_FIFO | + UART_FCR_CLEAR_RCVR | + UART_FCR_CLEAR_XMIT); + sdio_out(port, UART_FCR, 0); + + sdio_disable_func(port->func); + + sdio_uart_release_func(port); + +skip: + /* Free the transmit buffer page. */ + free_page((unsigned long)port->xmit.buf); +} + +static int sdio_uart_open (struct tty_struct *tty, struct file * filp) +{ + struct sdio_uart_port *port; + int ret; + + port = sdio_uart_port_get(tty->index); + if (!port) + return -ENODEV; + + mutex_lock(&port->open_lock); + + /* + * Make sure not to mess up with a dead port + * which has not been closed yet. + */ + if (tty->driver_data && tty->driver_data != port) { + mutex_unlock(&port->open_lock); + sdio_uart_port_put(port); + return -EBUSY; + } + + if (!port->opened) { + tty->driver_data = port; + port->tty = tty; + ret = sdio_uart_startup(port); + if (ret) { + tty->driver_data = NULL; + port->tty = NULL; + mutex_unlock(&port->open_lock); + sdio_uart_port_put(port); + return ret; + } + } + port->opened++; + mutex_unlock(&port->open_lock); + return 0; +} + +static void sdio_uart_close(struct tty_struct *tty, struct file * filp) +{ + struct sdio_uart_port *port = tty->driver_data; + + if (!port) + return; + + mutex_lock(&port->open_lock); + BUG_ON(!port->opened); + + /* + * This is messy. The tty layer calls us even when open() + * returned an error. Ignore this close request if tty->count + * is larger than port->count. + */ + if (tty->count > port->opened) { + mutex_unlock(&port->open_lock); + return; + } + + if (--port->opened == 0) { + tty->closing = 1; + sdio_uart_shutdown(port); + tty_ldisc_flush(tty); + port->tty = NULL; + tty->driver_data = NULL; + tty->closing = 0; + } + mutex_unlock(&port->open_lock); + sdio_uart_port_put(port); +} + +static int sdio_uart_write(struct tty_struct * tty, const unsigned char *buf, + int count) +{ + struct sdio_uart_port *port = tty->driver_data; + struct circ_buf *circ = &port->xmit; + int c, ret = 0; + + if (!port->func) + return -ENODEV; + + spin_lock(&port->write_lock); + while (1) { + c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE); + if (count < c) + c = count; + if (c <= 0) + break; + memcpy(circ->buf + circ->head, buf, c); + circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1); + buf += c; + count -= c; + ret += c; + } + spin_unlock(&port->write_lock); + + if ( !(port->ier & UART_IER_THRI)) { + int err = sdio_uart_claim_func(port); + if (!err) { + sdio_uart_start_tx(port); + sdio_uart_irq(port->func); + sdio_uart_release_func(port); + } else + ret = err; + } + + return ret; +} + +static int sdio_uart_write_room(struct tty_struct *tty) +{ + struct sdio_uart_port *port = tty->driver_data; + return port ? circ_chars_free(&port->xmit) : 0; +} + +static int sdio_uart_chars_in_buffer(struct tty_struct *tty) +{ + struct sdio_uart_port *port = tty->driver_data; + return port ? circ_chars_pending(&port->xmit) : 0; +} + +static void sdio_uart_send_xchar(struct tty_struct *tty, char ch) +{ + struct sdio_uart_port *port = tty->driver_data; + + port->x_char = ch; + if (ch && !(port->ier & UART_IER_THRI)) { + if (sdio_uart_claim_func(port) != 0) + return; + sdio_uart_start_tx(port); + sdio_uart_irq(port->func); + sdio_uart_release_func(port); + } +} + +static void sdio_uart_throttle(struct tty_struct *tty) +{ + struct sdio_uart_port *port = tty->driver_data; + + if (!I_IXOFF(tty) && !(tty->termios->c_cflag & CRTSCTS)) + return; + + if (sdio_uart_claim_func(port) != 0) + return; + + if (I_IXOFF(tty)) { + port->x_char = STOP_CHAR(tty); + sdio_uart_start_tx(port); + } + + if (tty->termios->c_cflag & CRTSCTS) + sdio_uart_clear_mctrl(port, TIOCM_RTS); + + sdio_uart_irq(port->func); + sdio_uart_release_func(port); +} + +static void sdio_uart_unthrottle(struct tty_struct *tty) +{ + struct sdio_uart_port *port = tty->driver_data; + + if (!I_IXOFF(tty) && !(tty->termios->c_cflag & CRTSCTS)) + return; + + if (sdio_uart_claim_func(port) != 0) + return; + + if (I_IXOFF(tty)) { + if (port->x_char) { + port->x_char = 0; + } else { + port->x_char = START_CHAR(tty); + sdio_uart_start_tx(port); + } + } + + if (tty->termios->c_cflag & CRTSCTS) + sdio_uart_set_mctrl(port, TIOCM_RTS); + + sdio_uart_irq(port->func); + sdio_uart_release_func(port); +} + +static void sdio_uart_set_termios(struct tty_struct *tty, struct ktermios *old_termios) +{ + struct sdio_uart_port *port = tty->driver_data; + unsigned int cflag = tty->termios->c_cflag; + +#define RELEVANT_IFLAG(iflag) ((iflag) & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK)) + + if ((cflag ^ old_termios->c_cflag) == 0 && + RELEVANT_IFLAG(tty->termios->c_iflag ^ old_termios->c_iflag) == 0) + return; + + if (sdio_uart_claim_func(port) != 0) + return; + + sdio_uart_change_speed(port, tty->termios, old_termios); + + /* Handle transition to B0 status */ + if ((old_termios->c_cflag & CBAUD) && !(cflag & CBAUD)) + sdio_uart_clear_mctrl(port, TIOCM_RTS | TIOCM_DTR); + + /* Handle transition away from B0 status */ + if (!(old_termios->c_cflag & CBAUD) && (cflag & CBAUD)) { + unsigned int mask = TIOCM_DTR; + if (!(cflag & CRTSCTS) || !test_bit(TTY_THROTTLED, &tty->flags)) + mask |= TIOCM_RTS; + sdio_uart_set_mctrl(port, mask); + } + + /* Handle turning off CRTSCTS */ + if ((old_termios->c_cflag & CRTSCTS) && !(cflag & CRTSCTS)) { + tty->hw_stopped = 0; + sdio_uart_start_tx(port); + } + + /* Handle turning on CRTSCTS */ + if (!(old_termios->c_cflag & CRTSCTS) && (cflag & CRTSCTS)) { + if (!(sdio_uart_get_mctrl(port) & TIOCM_CTS)) { + tty->hw_stopped = 1; + sdio_uart_stop_tx(port); + } + } + + sdio_uart_release_func(port); +} + +static void sdio_uart_break_ctl(struct tty_struct *tty, int break_state) +{ + struct sdio_uart_port *port = tty->driver_data; + + if (sdio_uart_claim_func(port) != 0) + return; + + if (break_state == -1) + port->lcr |= UART_LCR_SBC; + else + port->lcr &= ~UART_LCR_SBC; + sdio_out(port, UART_LCR, port->lcr); + + sdio_uart_release_func(port); +} + +static int sdio_uart_tiocmget(struct tty_struct *tty, struct file *file) +{ + struct sdio_uart_port *port = tty->driver_data; + int result; + + result = sdio_uart_claim_func(port); + if (!result) { + result = port->mctrl | sdio_uart_get_mctrl(port); + sdio_uart_release_func(port); + } + + return result; +} + +static int sdio_uart_tiocmset(struct tty_struct *tty, struct file *file, + unsigned int set, unsigned int clear) +{ + struct sdio_uart_port *port = tty->driver_data; + int result; + + result =sdio_uart_claim_func(port); + if(!result) { + sdio_uart_update_mctrl(port, set, clear); + sdio_uart_release_func(port); + } + + return result; +} + +static int sdio_uart_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int i, len = 0; + off_t begin = 0; + + len += sprintf(page, "serinfo:1.0 driver%s%s revision:%s\n", + "", "", ""); + for (i = 0; i < UART_NR && len < PAGE_SIZE - 96; i++) { + struct sdio_uart_port *port = sdio_uart_port_get(i); + if (port) { + len += sprintf(page+len, "%d: uart:SDIO", i); + if(capable(CAP_SYS_ADMIN)) { + len += sprintf(page + len, " tx:%d rx:%d", + port->icount.tx, port->icount.rx); + if (port->icount.frame) + len += sprintf(page + len, " fe:%d", + port->icount.frame); + if (port->icount.parity) + len += sprintf(page + len, " pe:%d", + port->icount.parity); + if (port->icount.brk) + len += sprintf(page + len, " brk:%d", + port->icount.brk); + if (port->icount.overrun) + len += sprintf(page + len, " oe:%d", + port->icount.overrun); + if (port->icount.cts) + len += sprintf(page + len, " cts:%d", + port->icount.cts); + if (port->icount.dsr) + len += sprintf(page + len, " dsr:%d", + port->icount.dsr); + if (port->icount.rng) + len += sprintf(page + len, " rng:%d", + port->icount.rng); + if (port->icount.dcd) + len += sprintf(page + len, " dcd:%d", + port->icount.dcd); + } + strcat(page, "\n"); + len++; + sdio_uart_port_put(port); + } + + if (len + begin > off + count) + goto done; + if (len + begin < off) { + begin += len; + len = 0; + } + } + *eof = 1; + +done: + if (off >= len + begin) + return 0; + *start = page + (off - begin); + return (count < begin + len - off) ? count : (begin + len - off); +} + +static const struct tty_operations sdio_uart_ops = { + .open = sdio_uart_open, + .close = sdio_uart_close, + .write = sdio_uart_write, + .write_room = sdio_uart_write_room, + .chars_in_buffer = sdio_uart_chars_in_buffer, + .send_xchar = sdio_uart_send_xchar, + .throttle = sdio_uart_throttle, + .unthrottle = sdio_uart_unthrottle, + .set_termios = sdio_uart_set_termios, + .break_ctl = sdio_uart_break_ctl, + .tiocmget = sdio_uart_tiocmget, + .tiocmset = sdio_uart_tiocmset, + .read_proc = sdio_uart_read_proc, +}; + +static struct tty_driver *sdio_uart_tty_driver; + +static int sdio_uart_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + struct sdio_uart_port *port; + int ret; + + port = kzalloc(sizeof(struct sdio_uart_port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + if (func->class == SDIO_CLASS_UART) { + printk(KERN_WARNING "%s: need info on UART class basic setup\n", + sdio_func_id(func)); + kfree(port); + return -ENOSYS; + } else if (func->class == SDIO_CLASS_GPS) { + /* + * We need tuple 0x91. It contains SUBTPL_SIOREG + * and SUBTPL_RCVCAPS. + */ + struct sdio_func_tuple *tpl; + for (tpl = func->tuples; tpl; tpl = tpl->next) { + if (tpl->code != 0x91) + continue; + if (tpl->size < 10) + continue; + if (tpl->data[1] == 0) /* SUBTPL_SIOREG */ + break; + } + if (!tpl) { + printk(KERN_WARNING + "%s: can't find tuple 0x91 subtuple 0 (SUBTPL_SIOREG) for GPS class\n", + sdio_func_id(func)); + kfree(port); + return -EINVAL; + } + printk(KERN_DEBUG "%s: Register ID = 0x%02x, Exp ID = 0x%02x\n", + sdio_func_id(func), tpl->data[2], tpl->data[3]); + port->regs_offset = (tpl->data[4] << 0) | + (tpl->data[5] << 8) | + (tpl->data[6] << 16); + printk(KERN_DEBUG "%s: regs offset = 0x%x\n", + sdio_func_id(func), port->regs_offset); + port->uartclk = tpl->data[7] * 115200; + if (port->uartclk == 0) + port->uartclk = 115200; + printk(KERN_DEBUG "%s: clk %d baudcode %u 4800-div %u\n", + sdio_func_id(func), port->uartclk, + tpl->data[7], tpl->data[8] | (tpl->data[9] << 8)); + } else { + kfree(port); + return -EINVAL; + } + + port->func = func; + sdio_set_drvdata(func, port); + + ret = sdio_uart_add_port(port); + if (ret) { + kfree(port); + } else { + struct device *dev; + dev = tty_register_device(sdio_uart_tty_driver, port->index, &func->dev); + if (IS_ERR(dev)) { + sdio_uart_port_remove(port); + ret = PTR_ERR(dev); + } + } + + return ret; +} + +static void sdio_uart_remove(struct sdio_func *func) +{ + struct sdio_uart_port *port = sdio_get_drvdata(func); + + tty_unregister_device(sdio_uart_tty_driver, port->index); + sdio_uart_port_remove(port); +} + +static const struct sdio_device_id sdio_uart_ids[] = { + { SDIO_DEVICE_CLASS(SDIO_CLASS_UART) }, + { SDIO_DEVICE_CLASS(SDIO_CLASS_GPS) }, + { /* end: all zeroes */ }, +}; + +MODULE_DEVICE_TABLE(sdio, sdio_uart_ids); + +static struct sdio_driver sdio_uart_driver = { + .probe = sdio_uart_probe, + .remove = sdio_uart_remove, + .name = "sdio_uart", + .id_table = sdio_uart_ids, +}; + +static int __init sdio_uart_init(void) +{ + int ret; + struct tty_driver *tty_drv; + + sdio_uart_tty_driver = tty_drv = alloc_tty_driver(UART_NR); + if (!tty_drv) + return -ENOMEM; + + tty_drv->owner = THIS_MODULE; + tty_drv->driver_name = "sdio_uart"; + tty_drv->name = "ttySDIO"; + tty_drv->major = 0; /* dynamically allocated */ + tty_drv->minor_start = 0; + tty_drv->type = TTY_DRIVER_TYPE_SERIAL; + tty_drv->subtype = SERIAL_TYPE_NORMAL; + tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; + tty_drv->init_termios = tty_std_termios; + tty_drv->init_termios.c_cflag = B4800 | CS8 | CREAD | HUPCL | CLOCAL; + tty_drv->init_termios.c_ispeed = 4800; + tty_drv->init_termios.c_ospeed = 4800; + tty_set_operations(tty_drv, &sdio_uart_ops); + + ret = tty_register_driver(tty_drv); + if (ret) + goto err1; + + ret = sdio_register_driver(&sdio_uart_driver); + if (ret) + goto err2; + + return 0; + +err2: + tty_unregister_driver(tty_drv); +err1: + put_tty_driver(tty_drv); + return ret; +} + +static void __exit sdio_uart_exit(void) +{ + sdio_unregister_driver(&sdio_uart_driver); + tty_unregister_driver(sdio_uart_tty_driver); + put_tty_driver(sdio_uart_tty_driver); +} + +module_init(sdio_uart_init); +module_exit(sdio_uart_exit); + +MODULE_AUTHOR("Nicolas Pitre"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mmc/core/Makefile b/drivers/mmc/core/Makefile index 3fdd08c7f143..4985807257a8 100644 --- a/drivers/mmc/core/Makefile +++ b/drivers/mmc/core/Makefile @@ -8,5 +8,7 @@ endif obj-$(CONFIG_MMC) += mmc_core.o mmc_core-y := core.o sysfs.o bus.o host.o \ - mmc.o mmc_ops.o sd.o sd_ops.o + mmc.o mmc_ops.o sd.o sd_ops.o \ + sdio.o sdio_ops.o sdio_bus.o \ + sdio_cis.o sdio_io.o sdio_irq.o diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index fe0e785ed7d2..d6b39e71e391 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -19,6 +19,7 @@ #include "sysfs.h" #include "core.h" +#include "sdio_cis.h" #include "bus.h" #define dev_to_mmc_card(d) container_of(d, struct mmc_card, dev) @@ -34,6 +35,8 @@ static ssize_t mmc_type_show(struct device *dev, return sprintf(buf, "MMC\n"); case MMC_TYPE_SD: return sprintf(buf, "SD\n"); + case MMC_TYPE_SDIO: + return sprintf(buf, "SDIO\n"); default: return -EFAULT; } @@ -59,28 +62,34 @@ mmc_bus_uevent(struct device *dev, char **envp, int num_envp, char *buf, int buf_size) { struct mmc_card *card = dev_to_mmc_card(dev); - int retval = 0, i = 0, length = 0; - -#define add_env(fmt,val) do { \ - retval = add_uevent_var(envp, num_envp, &i, \ - buf, buf_size, &length, \ - fmt, val); \ - if (retval) \ - return retval; \ -} while (0); + const char *type; + int i = 0, length = 0; switch (card->type) { case MMC_TYPE_MMC: - add_env("MMC_TYPE=%s", "MMC"); + type = "MMC"; break; case MMC_TYPE_SD: - add_env("MMC_TYPE=%s", "SD"); + type = "SD"; + break; + case MMC_TYPE_SDIO: + type = "SDIO"; break; + default: + type = NULL; } - add_env("MMC_NAME=%s", mmc_card_name(card)); + if (type) { + if (add_uevent_var(envp, num_envp, &i, + buf, buf_size, &length, + "MMC_TYPE=%s", type)) + return -ENOMEM; + } -#undef add_env + if (add_uevent_var(envp, num_envp, &i, + buf, buf_size, &length, + "MMC_NAME=%s", mmc_card_name(card))) + return -ENOMEM; envp[i] = NULL; @@ -176,6 +185,8 @@ static void mmc_release_card(struct device *dev) { struct mmc_card *card = dev_to_mmc_card(dev); + sdio_free_common_cis(card); + kfree(card); } @@ -223,6 +234,9 @@ int mmc_add_card(struct mmc_card *card) if (mmc_card_blockaddr(card)) type = "SDHC"; break; + case MMC_TYPE_SDIO: + type = "SDIO"; + break; default: type = "?"; break; diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index bfd2ae5bd669..5f6edf936f36 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -29,12 +29,15 @@ #include "core.h" #include "bus.h" #include "host.h" +#include "sdio_bus.h" #include "mmc_ops.h" #include "sd_ops.h" +#include "sdio_ops.h" extern int mmc_attach_mmc(struct mmc_host *host, u32 ocr); extern int mmc_attach_sd(struct mmc_host *host, u32 ocr); +extern int mmc_attach_sdio(struct mmc_host *host, u32 ocr); static struct workqueue_struct *workqueue; @@ -118,7 +121,7 @@ mmc_start_request(struct mmc_host *host, struct mmc_request *mrq) "tsac %d ms nsac %d\n", mmc_hostname(host), mrq->data->blksz, mrq->data->blocks, mrq->data->flags, - mrq->data->timeout_ns / 10000000, + mrq->data->timeout_ns / 1000000, mrq->data->timeout_clks); } @@ -199,7 +202,7 @@ int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries { struct mmc_request mrq; - BUG_ON(!host->claimed); + WARN_ON(!host->claimed); memset(&mrq, 0, sizeof(struct mmc_request)); @@ -231,6 +234,15 @@ void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card, unsigned int mult; /* + * SDIO cards only define an upper 1 s limit on access. + */ + if (mmc_card_sdio(card)) { + data->timeout_ns = 1000000000; + data->timeout_clks = 0; + return; + } + + /* * SD cards use a 100 multiplier rather than 10 */ mult = mmc_card_sd(card) ? 100 : 10; @@ -272,15 +284,20 @@ void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card, EXPORT_SYMBOL(mmc_set_data_timeout); /** - * mmc_claim_host - exclusively claim a host + * __mmc_claim_host - exclusively claim a host * @host: mmc host to claim + * @abort: whether or not the operation should be aborted * - * Claim a host for a set of operations. + * Claim a host for a set of operations. If @abort is non null and + * dereference a non-zero value then this will return prematurely with + * that non-zero value without acquiring the lock. Returns zero + * with the lock held otherwise. */ -void mmc_claim_host(struct mmc_host *host) +int __mmc_claim_host(struct mmc_host *host, atomic_t *abort) { DECLARE_WAITQUEUE(wait, current); unsigned long flags; + int stop; might_sleep(); @@ -288,19 +305,24 @@ void mmc_claim_host(struct mmc_host *host) spin_lock_irqsave(&host->lock, flags); while (1) { set_current_state(TASK_UNINTERRUPTIBLE); - if (!host->claimed) + stop = abort ? atomic_read(abort) : 0; + if (stop || !host->claimed) break; spin_unlock_irqrestore(&host->lock, flags); schedule(); spin_lock_irqsave(&host->lock, flags); } set_current_state(TASK_RUNNING); - host->claimed = 1; + if (!stop) + host->claimed = 1; + else + wake_up(&host->wq); spin_unlock_irqrestore(&host->lock, flags); remove_wait_queue(&host->wq, &wait); + return stop; } -EXPORT_SYMBOL(mmc_claim_host); +EXPORT_SYMBOL(__mmc_claim_host); /** * mmc_release_host - release a host @@ -313,7 +335,7 @@ void mmc_release_host(struct mmc_host *host) { unsigned long flags; - BUG_ON(!host->claimed); + WARN_ON(!host->claimed); spin_lock_irqsave(&host->lock, flags); host->claimed = 0; @@ -511,7 +533,7 @@ void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops) BUG_ON(!host); BUG_ON(!ops); - BUG_ON(!host->claimed); + WARN_ON(!host->claimed); spin_lock_irqsave(&host->lock, flags); @@ -535,8 +557,8 @@ void mmc_detach_bus(struct mmc_host *host) BUG_ON(!host); - BUG_ON(!host->claimed); - BUG_ON(!host->bus_ops); + WARN_ON(!host->claimed); + WARN_ON(!host->bus_ops); spin_lock_irqsave(&host->lock, flags); @@ -564,7 +586,7 @@ void mmc_detect_change(struct mmc_host *host, unsigned long delay) #ifdef CONFIG_MMC_DEBUG unsigned long flags; spin_lock_irqsave(&host->lock, flags); - BUG_ON(host->removed); + WARN_ON(host->removed); spin_unlock_irqrestore(&host->lock, flags); #endif @@ -597,24 +619,38 @@ void mmc_rescan(struct work_struct *work) mmc_send_if_cond(host, host->ocr_avail); + /* + * First we search for SDIO... + */ + err = mmc_send_io_op_cond(host, 0, &ocr); + if (!err) { + if (mmc_attach_sdio(host, ocr)) + mmc_power_off(host); + return; + } + + /* + * ...then normal SD... + */ err = mmc_send_app_op_cond(host, 0, &ocr); - if (err == MMC_ERR_NONE) { + if (!err) { if (mmc_attach_sd(host, ocr)) mmc_power_off(host); - } else { - /* - * If we fail to detect any SD cards then try - * searching for MMC cards. - */ - err = mmc_send_op_cond(host, 0, &ocr); - if (err == MMC_ERR_NONE) { - if (mmc_attach_mmc(host, ocr)) - mmc_power_off(host); - } else { + return; + } + + /* + * ...and finally MMC. + */ + err = mmc_send_op_cond(host, 0, &ocr); + if (!err) { + if (mmc_attach_mmc(host, ocr)) mmc_power_off(host); - mmc_release_host(host); - } + return; } + + mmc_release_host(host); + mmc_power_off(host); } else { if (host->bus_ops->detect && !host->bus_dead) host->bus_ops->detect(host); @@ -725,22 +761,38 @@ static int __init mmc_init(void) return -ENOMEM; ret = mmc_register_bus(); - if (ret == 0) { - ret = mmc_register_host_class(); - if (ret) - mmc_unregister_bus(); - } + if (ret) + goto destroy_workqueue; + + ret = mmc_register_host_class(); + if (ret) + goto unregister_bus; + + ret = sdio_register_bus(); + if (ret) + goto unregister_host_class; + + return 0; + +unregister_host_class: + mmc_unregister_host_class(); +unregister_bus: + mmc_unregister_bus(); +destroy_workqueue: + destroy_workqueue(workqueue); + return ret; } static void __exit mmc_exit(void) { + sdio_unregister_bus(); mmc_unregister_host_class(); mmc_unregister_bus(); destroy_workqueue(workqueue); } -module_init(mmc_init); +subsys_initcall(mmc_init); module_exit(mmc_exit); MODULE_LICENSE("GPL"); diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index 6a7e29849603..62fc76b267d2 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -102,6 +102,9 @@ int mmc_add_host(struct mmc_host *host) { int err; + WARN_ON((host->caps & MMC_CAP_SDIO_IRQ) && + !host->ops->enable_sdio_irq); + if (!idr_pre_get(&mmc_host_idr, GFP_KERNEL)) return -ENOMEM; diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 21d7f48e1d4e..aec56fc4fa42 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -164,10 +164,10 @@ static int mmc_read_ext_csd(struct mmc_card *card) BUG_ON(!card); - err = MMC_ERR_FAILED; + err = -EIO; if (card->csd.mmca_vsn < CSD_SPEC_VER_4) - return MMC_ERR_NONE; + return 0; /* * As the ext_csd is so large and mostly unused, we don't store the @@ -176,13 +176,19 @@ static int mmc_read_ext_csd(struct mmc_card *card) ext_csd = kmalloc(512, GFP_KERNEL); if (!ext_csd) { printk(KERN_ERR "%s: could not allocate a buffer to " - "receive the ext_csd. mmc v4 cards will be " - "treated as v3.\n", mmc_hostname(card->host)); - return MMC_ERR_FAILED; + "receive the ext_csd.\n", mmc_hostname(card->host)); + return -ENOMEM; } err = mmc_send_ext_csd(card, ext_csd); - if (err != MMC_ERR_NONE) { + if (err) { + /* + * We all hosts that cannot perform the command + * to fail more gracefully + */ + if (err != -EINVAL) + goto out; + /* * High capacity cards should have this "magic" size * stored in their CSD. @@ -197,8 +203,9 @@ static int mmc_read_ext_csd(struct mmc_card *card) "EXT_CSD, performance might " "suffer.\n", mmc_hostname(card->host)); - err = MMC_ERR_NONE; + err = 0; } + goto out; } @@ -246,7 +253,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, unsigned int max_dtr; BUG_ON(!host); - BUG_ON(!host->claimed); + WARN_ON(!host->claimed); /* * Since we're changing the OCR value, we seem to @@ -258,19 +265,21 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, /* The extra bit indicates that we support high capacity */ err = mmc_send_op_cond(host, ocr | (1 << 30), NULL); - if (err != MMC_ERR_NONE) + if (err) goto err; /* * Fetch CID from card. */ err = mmc_all_send_cid(host, cid); - if (err != MMC_ERR_NONE) + if (err) goto err; if (oldcard) { - if (memcmp(cid, oldcard->raw_cid, sizeof(cid)) != 0) + if (memcmp(cid, oldcard->raw_cid, sizeof(cid)) != 0) { + err = -ENOENT; goto err; + } card = oldcard; } else { @@ -278,8 +287,10 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, * Allocate card structure. */ card = mmc_alloc_card(host); - if (IS_ERR(card)) + if (IS_ERR(card)) { + err = PTR_ERR(card); goto err; + } card->type = MMC_TYPE_MMC; card->rca = 1; @@ -290,7 +301,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, * Set card RCA. */ err = mmc_set_relative_addr(card); - if (err != MMC_ERR_NONE) + if (err) goto free_card; mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); @@ -300,14 +311,14 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, * Fetch CSD from card. */ err = mmc_send_csd(card, card->raw_csd); - if (err != MMC_ERR_NONE) + if (err) goto free_card; err = mmc_decode_csd(card); - if (err < 0) + if (err) goto free_card; err = mmc_decode_cid(card); - if (err < 0) + if (err) goto free_card; } @@ -315,7 +326,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, * Select card, as all following commands rely on that. */ err = mmc_select_card(card); - if (err != MMC_ERR_NONE) + if (err) goto free_card; if (!oldcard) { @@ -323,7 +334,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, * Fetch and process extened CSD. */ err = mmc_read_ext_csd(card); - if (err != MMC_ERR_NONE) + if (err) goto free_card; } @@ -334,7 +345,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, (host->caps & MMC_CAP_MMC_HIGHSPEED)) { err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_HS_TIMING, 1); - if (err != MMC_ERR_NONE) + if (err) goto free_card; mmc_card_set_highspeed(card); @@ -363,7 +374,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, (host->caps & MMC_CAP_4_BIT_DATA)) { err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BUS_WIDTH, EXT_CSD_BUS_WIDTH_4); - if (err != MMC_ERR_NONE) + if (err) goto free_card; mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4); @@ -372,14 +383,14 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr, if (!oldcard) host->card = card; - return MMC_ERR_NONE; + return 0; free_card: if (!oldcard) mmc_remove_card(card); err: - return MMC_ERR_FAILED; + return err; } /* @@ -413,7 +424,7 @@ static void mmc_detect(struct mmc_host *host) mmc_release_host(host); - if (err != MMC_ERR_NONE) { + if (err) { mmc_remove(host); mmc_claim_host(host); @@ -502,7 +513,7 @@ static void mmc_resume(struct mmc_host *host) err = mmc_init_card(host, host->ocr, host->card); mmc_release_host(host); - if (err != MMC_ERR_NONE) { + if (err) { mmc_remove(host); mmc_claim_host(host); @@ -536,7 +547,7 @@ int mmc_attach_mmc(struct mmc_host *host, u32 ocr) int err; BUG_ON(!host); - BUG_ON(!host->claimed); + WARN_ON(!host->claimed); mmc_attach_bus(host, &mmc_ops); @@ -565,7 +576,7 @@ int mmc_attach_mmc(struct mmc_host *host, u32 ocr) * Detect and init the card. */ err = mmc_init_card(host, host->ocr, NULL); - if (err != MMC_ERR_NONE) + if (err) goto err; mmc_release_host(host); @@ -587,6 +598,6 @@ err: printk(KERN_ERR "%s: error %d whilst initialising MMC card\n", mmc_hostname(host), err); - return 0; + return err; } diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c index 913e75f00843..15cd575effaa 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -40,10 +40,10 @@ static int _mmc_select_card(struct mmc_host *host, struct mmc_card *card) } err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) return err; - return MMC_ERR_NONE; + return 0; } int mmc_select_card(struct mmc_card *card) @@ -99,13 +99,13 @@ int mmc_send_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr) for (i = 100; i; i--) { err = mmc_wait_for_cmd(host, &cmd, 0); - if (err != MMC_ERR_NONE) + if (err) break; if (cmd.resp[0] & MMC_CARD_BUSY || ocr == 0) break; - err = MMC_ERR_TIMEOUT; + err = -ETIMEDOUT; mmc_delay(10); } @@ -131,12 +131,12 @@ int mmc_all_send_cid(struct mmc_host *host, u32 *cid) cmd.flags = MMC_RSP_R2 | MMC_CMD_BCR; err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) return err; memcpy(cid, cmd.resp, sizeof(u32) * 4); - return MMC_ERR_NONE; + return 0; } int mmc_set_relative_addr(struct mmc_card *card) @@ -154,10 +154,10 @@ int mmc_set_relative_addr(struct mmc_card *card) cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) return err; - return MMC_ERR_NONE; + return 0; } int mmc_send_csd(struct mmc_card *card, u32 *csd) @@ -176,12 +176,12 @@ int mmc_send_csd(struct mmc_card *card, u32 *csd) cmd.flags = MMC_RSP_R2 | MMC_CMD_AC; err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) return err; memcpy(csd, cmd.resp, sizeof(u32) * 4); - return MMC_ERR_NONE; + return 0; } int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd) @@ -218,12 +218,12 @@ int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd) mmc_wait_for_req(card->host, &mrq); - if (cmd.error != MMC_ERR_NONE) + if (cmd.error) return cmd.error; - if (data.error != MMC_ERR_NONE) + if (data.error) return data.error; - return MMC_ERR_NONE; + return 0; } int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value) @@ -244,10 +244,10 @@ int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value) cmd.flags = MMC_RSP_R1B | MMC_CMD_AC; err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) return err; - return MMC_ERR_NONE; + return 0; } int mmc_send_status(struct mmc_card *card, u32 *status) @@ -265,12 +265,12 @@ int mmc_send_status(struct mmc_card *card, u32 *status) cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) return err; if (status) *status = cmd.resp[0]; - return MMC_ERR_NONE; + return 0; } diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 1edc62b1e5c6..c86588fdaae5 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -166,8 +166,6 @@ static int mmc_decode_scr(struct mmc_card *card) unsigned int scr_struct; u32 resp[4]; - BUG_ON(!mmc_card_sd(card)); - resp[3] = card->raw_scr[1]; resp[2] = card->raw_scr[0]; @@ -193,30 +191,38 @@ static int mmc_read_switch(struct mmc_card *card) u8 *status; if (card->scr.sda_vsn < SCR_SPEC_VER_1) - return MMC_ERR_NONE; + return 0; if (!(card->csd.cmdclass & CCC_SWITCH)) { printk(KERN_WARNING "%s: card lacks mandatory switch " "function, performance might suffer.\n", mmc_hostname(card->host)); - return MMC_ERR_NONE; + return 0; } - err = MMC_ERR_FAILED; + err = -EIO; status = kmalloc(64, GFP_KERNEL); if (!status) { printk(KERN_ERR "%s: could not allocate a buffer for " "switch capabilities.\n", mmc_hostname(card->host)); - return err; + return -ENOMEM; } err = mmc_sd_switch(card, 0, 0, 1, status); - if (err != MMC_ERR_NONE) { + if (err) { + /* + * We all hosts that cannot perform the command + * to fail more gracefully + */ + if (err != -EINVAL) + goto out; + printk(KERN_WARNING "%s: problem reading switch " "capabilities, performance might suffer.\n", mmc_hostname(card->host)); - err = MMC_ERR_NONE; + err = 0; + goto out; } @@ -238,28 +244,28 @@ static int mmc_switch_hs(struct mmc_card *card) u8 *status; if (card->scr.sda_vsn < SCR_SPEC_VER_1) - return MMC_ERR_NONE; + return 0; if (!(card->csd.cmdclass & CCC_SWITCH)) - return MMC_ERR_NONE; + return 0; if (!(card->host->caps & MMC_CAP_SD_HIGHSPEED)) - return MMC_ERR_NONE; + return 0; if (card->sw_caps.hs_max_dtr == 0) - return MMC_ERR_NONE; + return 0; - err = MMC_ERR_FAILED; + err = -EIO; status = kmalloc(64, GFP_KERNEL); if (!status) { printk(KERN_ERR "%s: could not allocate a buffer for " "switch capabilities.\n", mmc_hostname(card->host)); - return err; + return -ENOMEM; } err = mmc_sd_switch(card, 1, 0, 1, status); - if (err != MMC_ERR_NONE) + if (err) goto out; if ((status[16] & 0xF) != 1) { @@ -292,7 +298,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, unsigned int max_dtr; BUG_ON(!host); - BUG_ON(!host->claimed); + WARN_ON(!host->claimed); /* * Since we're changing the OCR value, we seem to @@ -309,23 +315,25 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, * block-addressed SDHC cards. */ err = mmc_send_if_cond(host, ocr); - if (err == MMC_ERR_NONE) + if (!err) ocr |= 1 << 30; err = mmc_send_app_op_cond(host, ocr, NULL); - if (err != MMC_ERR_NONE) + if (err) goto err; /* * Fetch CID from card. */ err = mmc_all_send_cid(host, cid); - if (err != MMC_ERR_NONE) + if (err) goto err; if (oldcard) { - if (memcmp(cid, oldcard->raw_cid, sizeof(cid)) != 0) + if (memcmp(cid, oldcard->raw_cid, sizeof(cid)) != 0) { + err = -ENOENT; goto err; + } card = oldcard; } else { @@ -333,8 +341,10 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, * Allocate card structure. */ card = mmc_alloc_card(host); - if (IS_ERR(card)) + if (IS_ERR(card)) { + err = PTR_ERR(card); goto err; + } card->type = MMC_TYPE_SD; memcpy(card->raw_cid, cid, sizeof(card->raw_cid)); @@ -344,7 +354,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, * Set card RCA. */ err = mmc_send_relative_addr(host, &card->rca); - if (err != MMC_ERR_NONE) + if (err) goto free_card; mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); @@ -354,11 +364,11 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, * Fetch CSD from card. */ err = mmc_send_csd(card, card->raw_csd); - if (err != MMC_ERR_NONE) + if (err) goto free_card; err = mmc_decode_csd(card); - if (err < 0) + if (err) goto free_card; mmc_decode_cid(card); @@ -368,7 +378,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, * Select card, as all following commands rely on that. */ err = mmc_select_card(card); - if (err != MMC_ERR_NONE) + if (err) goto free_card; if (!oldcard) { @@ -376,7 +386,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, * Fetch SCR from card. */ err = mmc_app_send_scr(card, card->raw_scr); - if (err != MMC_ERR_NONE) + if (err) goto free_card; err = mmc_decode_scr(card); @@ -387,7 +397,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, * Fetch switch information from card. */ err = mmc_read_switch(card); - if (err != MMC_ERR_NONE) + if (err) goto free_card; } @@ -395,7 +405,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, * Attempt to change to high-speed (if supported) */ err = mmc_switch_hs(card); - if (err != MMC_ERR_NONE) + if (err) goto free_card; /* @@ -418,7 +428,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, if ((host->caps & MMC_CAP_4_BIT_DATA) && (card->scr.bus_widths & SD_SCR_BUS_WIDTH_4)) { err = mmc_app_set_bus_width(card, MMC_BUS_WIDTH_4); - if (err != MMC_ERR_NONE) + if (err) goto free_card; mmc_set_bus_width(host, MMC_BUS_WIDTH_4); @@ -442,14 +452,14 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr, if (!oldcard) host->card = card; - return MMC_ERR_NONE; + return 0; free_card: if (!oldcard) mmc_remove_card(card); err: - return MMC_ERR_FAILED; + return err; } /* @@ -483,7 +493,7 @@ static void mmc_sd_detect(struct mmc_host *host) mmc_release_host(host); - if (err != MMC_ERR_NONE) { + if (err) { mmc_sd_remove(host); mmc_claim_host(host); @@ -574,7 +584,7 @@ static void mmc_sd_resume(struct mmc_host *host) err = mmc_sd_init_card(host, host->ocr, host->card); mmc_release_host(host); - if (err != MMC_ERR_NONE) { + if (err) { mmc_sd_remove(host); mmc_claim_host(host); @@ -608,7 +618,7 @@ int mmc_attach_sd(struct mmc_host *host, u32 ocr) int err; BUG_ON(!host); - BUG_ON(!host->claimed); + WARN_ON(!host->claimed); mmc_attach_bus(host, &mmc_sd_ops); @@ -644,7 +654,7 @@ int mmc_attach_sd(struct mmc_host *host, u32 ocr) * Detect and init the card. */ err = mmc_sd_init_card(host, host->ocr, NULL); - if (err != MMC_ERR_NONE) + if (err) goto err; mmc_release_host(host); @@ -666,6 +676,6 @@ err: printk(KERN_ERR "%s: error %d whilst initialising SD card\n", mmc_hostname(host), err); - return 0; + return err; } diff --git a/drivers/mmc/core/sd_ops.c b/drivers/mmc/core/sd_ops.c index 342f340ebc25..b4d43bd0fedd 100644 --- a/drivers/mmc/core/sd_ops.c +++ b/drivers/mmc/core/sd_ops.c @@ -40,14 +40,14 @@ static int mmc_app_cmd(struct mmc_host *host, struct mmc_card *card) } err = mmc_wait_for_cmd(host, &cmd, 0); - if (err != MMC_ERR_NONE) + if (err) return err; /* Check that card supported application commands */ if (!(cmd.resp[0] & R1_APP_CMD)) - return MMC_ERR_FAILED; + return -EOPNOTSUPP; - return MMC_ERR_NONE; + return 0; } /** @@ -73,7 +73,7 @@ int mmc_wait_for_app_cmd(struct mmc_host *host, struct mmc_card *card, BUG_ON(!cmd); BUG_ON(retries < 0); - err = MMC_ERR_INVALID; + err = -EIO; /* * We have to resend MMC_APP_CMD for each attempt so @@ -83,7 +83,7 @@ int mmc_wait_for_app_cmd(struct mmc_host *host, struct mmc_card *card, memset(&mrq, 0, sizeof(struct mmc_request)); err = mmc_app_cmd(host, card); - if (err != MMC_ERR_NONE) + if (err) continue; memset(&mrq, 0, sizeof(struct mmc_request)); @@ -97,7 +97,7 @@ int mmc_wait_for_app_cmd(struct mmc_host *host, struct mmc_card *card, mmc_wait_for_req(host, &mrq); err = cmd->error; - if (cmd->error == MMC_ERR_NONE) + if (!cmd->error) break; } @@ -127,14 +127,14 @@ int mmc_app_set_bus_width(struct mmc_card *card, int width) cmd.arg = SD_BUS_WIDTH_4; break; default: - return MMC_ERR_INVALID; + return -EINVAL; } err = mmc_wait_for_app_cmd(card->host, card, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) return err; - return MMC_ERR_NONE; + return 0; } int mmc_send_app_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr) @@ -152,13 +152,13 @@ int mmc_send_app_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr) for (i = 100; i; i--) { err = mmc_wait_for_app_cmd(host, NULL, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) break; if (cmd.resp[0] & MMC_CARD_BUSY || ocr == 0) break; - err = MMC_ERR_TIMEOUT; + err = -ETIMEDOUT; mmc_delay(10); } @@ -185,13 +185,13 @@ int mmc_send_if_cond(struct mmc_host *host, u32 ocr) cmd.flags = MMC_RSP_R7 | MMC_CMD_BCR; err = mmc_wait_for_cmd(host, &cmd, 0); - if (err != MMC_ERR_NONE) + if (err) return err; if ((cmd.resp[0] & 0xFF) != test_pattern) - return MMC_ERR_FAILED; + return -EIO; - return MMC_ERR_NONE; + return 0; } int mmc_send_relative_addr(struct mmc_host *host, unsigned int *rca) @@ -209,12 +209,12 @@ int mmc_send_relative_addr(struct mmc_host *host, unsigned int *rca) cmd.flags = MMC_RSP_R6 | MMC_CMD_BCR; err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES); - if (err != MMC_ERR_NONE) + if (err) return err; *rca = cmd.resp[0] >> 16; - return MMC_ERR_NONE; + return 0; } int mmc_app_send_scr(struct mmc_card *card, u32 *scr) @@ -230,7 +230,7 @@ int mmc_app_send_scr(struct mmc_card *card, u32 *scr) BUG_ON(!scr); err = mmc_app_cmd(card->host, card); - if (err != MMC_ERR_NONE) + if (err) return err; memset(&mrq, 0, sizeof(struct mmc_request)); @@ -256,15 +256,15 @@ int mmc_app_send_scr(struct mmc_card *card, u32 *scr) mmc_wait_for_req(card->host, &mrq); - if (cmd.error != MMC_ERR_NONE) + if (cmd.error) return cmd.error; - if (data.error != MMC_ERR_NONE) + if (data.error) return data.error; scr[0] = ntohl(scr[0]); scr[1] = ntohl(scr[1]); - return MMC_ERR_NONE; + return 0; } int mmc_sd_switch(struct mmc_card *card, int mode, int group, @@ -306,11 +306,11 @@ int mmc_sd_switch(struct mmc_card *card, int mode, int group, mmc_wait_for_req(card->host, &mrq); - if (cmd.error != MMC_ERR_NONE) + if (cmd.error) return cmd.error; - if (data.error != MMC_ERR_NONE) + if (data.error) return data.error; - return MMC_ERR_NONE; + return 0; } diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c new file mode 100644 index 000000000000..2f3fb994c383 --- /dev/null +++ b/drivers/mmc/core/sdio.c @@ -0,0 +1,382 @@ +/* + * linux/drivers/mmc/sdio.c + * + * Copyright 2006-2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#include <linux/err.h> + +#include <linux/mmc/host.h> +#include <linux/mmc/card.h> +#include <linux/mmc/sdio.h> +#include <linux/mmc/sdio_func.h> + +#include "core.h" +#include "bus.h" +#include "sdio_bus.h" +#include "mmc_ops.h" +#include "sd_ops.h" +#include "sdio_ops.h" +#include "sdio_cis.h" + +static int sdio_read_fbr(struct sdio_func *func) +{ + int ret; + unsigned char data; + + ret = mmc_io_rw_direct(func->card, 0, 0, + SDIO_FBR_BASE(func->num) + SDIO_FBR_STD_IF, 0, &data); + if (ret) + goto out; + + data &= 0x0f; + + if (data == 0x0f) { + ret = mmc_io_rw_direct(func->card, 0, 0, + SDIO_FBR_BASE(func->num) + SDIO_FBR_STD_IF_EXT, 0, &data); + if (ret) + goto out; + } + + func->class = data; + +out: + return ret; +} + +static int sdio_init_func(struct mmc_card *card, unsigned int fn) +{ + int ret; + struct sdio_func *func; + + BUG_ON(fn > SDIO_MAX_FUNCS); + + func = sdio_alloc_func(card); + if (IS_ERR(func)) + return PTR_ERR(func); + + func->num = fn; + + ret = sdio_read_fbr(func); + if (ret) + goto fail; + + ret = sdio_read_func_cis(func); + if (ret) + goto fail; + + card->sdio_func[fn - 1] = func; + + return 0; + +fail: + /* + * It is okay to remove the function here even though we hold + * the host lock as we haven't registered the device yet. + */ + sdio_remove_func(func); + return ret; +} + +static int sdio_read_cccr(struct mmc_card *card) +{ + int ret; + int cccr_vsn; + unsigned char data; + + memset(&card->cccr, 0, sizeof(struct sdio_cccr)); + + ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_CCCR, 0, &data); + if (ret) + goto out; + + cccr_vsn = data & 0x0f; + + if (cccr_vsn > SDIO_CCCR_REV_1_20) { + printk(KERN_ERR "%s: unrecognised CCCR structure version %d\n", + mmc_hostname(card->host), cccr_vsn); + return -EINVAL; + } + + card->cccr.sdio_vsn = (data & 0xf0) >> 4; + + ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_CAPS, 0, &data); + if (ret) + goto out; + + if (data & SDIO_CCCR_CAP_SMB) + card->cccr.multi_block = 1; + if (data & SDIO_CCCR_CAP_LSC) + card->cccr.low_speed = 1; + if (data & SDIO_CCCR_CAP_4BLS) + card->cccr.wide_bus = 1; + + if (cccr_vsn >= SDIO_CCCR_REV_1_10) { + ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_POWER, 0, &data); + if (ret) + goto out; + + if (data & SDIO_POWER_SMPC) + card->cccr.high_power = 1; + } + + if (cccr_vsn >= SDIO_CCCR_REV_1_20) { + ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_SPEED, 0, &data); + if (ret) + goto out; + + if (data & SDIO_SPEED_SHS) + card->cccr.high_speed = 1; + } + +out: + return ret; +} + +static int sdio_enable_wide(struct mmc_card *card) +{ + int ret; + u8 ctrl; + + if (!(card->host->caps & MMC_CAP_4_BIT_DATA)) + return 0; + + if (card->cccr.low_speed && !card->cccr.wide_bus) + return 0; + + ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_IF, 0, &ctrl); + if (ret) + return ret; + + ctrl |= SDIO_BUS_WIDTH_4BIT; + + ret = mmc_io_rw_direct(card, 1, 0, SDIO_CCCR_IF, ctrl, NULL); + if (ret) + return ret; + + mmc_set_bus_width(card->host, MMC_BUS_WIDTH_4); + + return 0; +} + +/* + * Host is being removed. Free up the current card. + */ +static void mmc_sdio_remove(struct mmc_host *host) +{ + int i; + + BUG_ON(!host); + BUG_ON(!host->card); + + for (i = 0;i < host->card->sdio_funcs;i++) { + if (host->card->sdio_func[i]) { + sdio_remove_func(host->card->sdio_func[i]); + host->card->sdio_func[i] = NULL; + } + } + + mmc_remove_card(host->card); + host->card = NULL; +} + +/* + * Card detection callback from host. + */ +static void mmc_sdio_detect(struct mmc_host *host) +{ + int err; + + BUG_ON(!host); + BUG_ON(!host->card); + + mmc_claim_host(host); + + /* + * Just check if our card has been removed. + */ + err = mmc_select_card(host->card); + + mmc_release_host(host); + + if (err) { + mmc_sdio_remove(host); + + mmc_claim_host(host); + mmc_detach_bus(host); + mmc_release_host(host); + } +} + + +static const struct mmc_bus_ops mmc_sdio_ops = { + .remove = mmc_sdio_remove, + .detect = mmc_sdio_detect, +}; + + +/* + * Starting point for SDIO card init. + */ +int mmc_attach_sdio(struct mmc_host *host, u32 ocr) +{ + int err; + int i, funcs; + struct mmc_card *card; + + BUG_ON(!host); + WARN_ON(!host->claimed); + + mmc_attach_bus(host, &mmc_sdio_ops); + + /* + * Sanity check the voltages that the card claims to + * support. + */ + if (ocr & 0x7F) { + printk(KERN_WARNING "%s: card claims to support voltages " + "below the defined range. These will be ignored.\n", + mmc_hostname(host)); + ocr &= ~0x7F; + } + + if (ocr & MMC_VDD_165_195) { + printk(KERN_WARNING "%s: SDIO card claims to support the " + "incompletely defined 'low voltage range'. This " + "will be ignored.\n", mmc_hostname(host)); + ocr &= ~MMC_VDD_165_195; + } + + host->ocr = mmc_select_voltage(host, ocr); + + /* + * Can we support the voltage(s) of the card(s)? + */ + if (!host->ocr) { + err = -EINVAL; + goto err; + } + + /* + * Inform the card of the voltage + */ + err = mmc_send_io_op_cond(host, host->ocr, &ocr); + if (err) + goto err; + + /* + * The number of functions on the card is encoded inside + * the ocr. + */ + funcs = (ocr & 0x70000000) >> 28; + + /* + * Allocate card structure. + */ + card = mmc_alloc_card(host); + if (IS_ERR(card)) { + err = PTR_ERR(card); + goto err; + } + + card->type = MMC_TYPE_SDIO; + card->sdio_funcs = funcs; + + host->card = card; + + /* + * Set card RCA. + */ + err = mmc_send_relative_addr(host, &card->rca); + if (err) + goto remove; + + mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL); + + /* + * Select card, as all following commands rely on that. + */ + err = mmc_select_card(card); + if (err) + goto remove; + + /* + * Read the common registers. + */ + err = sdio_read_cccr(card); + if (err) + goto remove; + + /* + * Read the common CIS tuples. + */ + err = sdio_read_common_cis(card); + if (err) + goto remove; + + /* + * No support for high-speed yet, so just set + * the card's maximum speed. + */ + mmc_set_clock(host, card->cis.max_dtr); + + /* + * Switch to wider bus (if supported). + */ + err = sdio_enable_wide(card); + if (err) + goto remove; + + /* + * Initialize (but don't add) all present functions. + */ + for (i = 0;i < funcs;i++) { + err = sdio_init_func(host->card, i + 1); + if (err) + goto remove; + } + + mmc_release_host(host); + + /* + * First add the card to the driver model... + */ + err = mmc_add_card(host->card); + if (err) + goto remove_added; + + /* + * ...then the SDIO functions. + */ + for (i = 0;i < funcs;i++) { + err = sdio_add_func(host->card->sdio_func[i]); + if (err) + goto remove_added; + } + + return 0; + + +remove_added: + /* Remove without lock if the device has been added. */ + mmc_sdio_remove(host); + mmc_claim_host(host); +remove: + /* And with lock if it hasn't been added. */ + if (host->card) + mmc_sdio_remove(host); +err: + mmc_detach_bus(host); + mmc_release_host(host); + + printk(KERN_ERR "%s: error %d whilst initialising SDIO card\n", + mmc_hostname(host), err); + + return err; +} + diff --git a/drivers/mmc/core/sdio_bus.c b/drivers/mmc/core/sdio_bus.c new file mode 100644 index 000000000000..c17692594c56 --- /dev/null +++ b/drivers/mmc/core/sdio_bus.c @@ -0,0 +1,269 @@ +/* + * linux/drivers/mmc/core/sdio_bus.c + * + * Copyright 2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * SDIO function driver model + */ + +#include <linux/device.h> +#include <linux/err.h> + +#include <linux/mmc/card.h> +#include <linux/mmc/sdio_func.h> + +#include "sdio_cis.h" +#include "sdio_bus.h" + +#define dev_to_sdio_func(d) container_of(d, struct sdio_func, dev) +#define to_sdio_driver(d) container_of(d, struct sdio_driver, drv) + +/* show configuration fields */ +#define sdio_config_attr(field, format_string) \ +static ssize_t \ +field##_show(struct device *dev, struct device_attribute *attr, char *buf) \ +{ \ + struct sdio_func *func; \ + \ + func = dev_to_sdio_func (dev); \ + return sprintf (buf, format_string, func->field); \ +} + +sdio_config_attr(class, "0x%02x\n"); +sdio_config_attr(vendor, "0x%04x\n"); +sdio_config_attr(device, "0x%04x\n"); + +static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sdio_func *func = dev_to_sdio_func (dev); + + return sprintf(buf, "sdio:c%02Xv%04Xd%04X\n", + func->class, func->vendor, func->device); +} + +static struct device_attribute sdio_dev_attrs[] = { + __ATTR_RO(class), + __ATTR_RO(vendor), + __ATTR_RO(device), + __ATTR_RO(modalias), + __ATTR_NULL, +}; + +static const struct sdio_device_id *sdio_match_one(struct sdio_func *func, + const struct sdio_device_id *id) +{ + if (id->class != (__u8)SDIO_ANY_ID && id->class != func->class) + return NULL; + if (id->vendor != (__u16)SDIO_ANY_ID && id->vendor != func->vendor) + return NULL; + if (id->device != (__u16)SDIO_ANY_ID && id->device != func->device) + return NULL; + return id; +} + +static const struct sdio_device_id *sdio_match_device(struct sdio_func *func, + struct sdio_driver *sdrv) +{ + const struct sdio_device_id *ids; + + ids = sdrv->id_table; + + if (ids) { + while (ids->class || ids->vendor || ids->device) { + if (sdio_match_one(func, ids)) + return ids; + ids++; + } + } + + return NULL; +} + +static int sdio_bus_match(struct device *dev, struct device_driver *drv) +{ + struct sdio_func *func = dev_to_sdio_func(dev); + struct sdio_driver *sdrv = to_sdio_driver(drv); + + if (sdio_match_device(func, sdrv)) + return 1; + + return 0; +} + +static int +sdio_bus_uevent(struct device *dev, char **envp, int num_envp, char *buf, + int buf_size) +{ + struct sdio_func *func = dev_to_sdio_func(dev); + int i = 0, length = 0; + + if (add_uevent_var(envp, num_envp, &i, + buf, buf_size, &length, + "SDIO_CLASS=%02X", func->class)) + return -ENOMEM; + + if (add_uevent_var(envp, num_envp, &i, + buf, buf_size, &length, + "SDIO_ID=%04X:%04X", func->vendor, func->device)) + return -ENOMEM; + + if (add_uevent_var(envp, num_envp, &i, + buf, buf_size, &length, + "MODALIAS=sdio:c%02Xv%04Xd%04X", + func->class, func->vendor, func->device)) + return -ENOMEM; + + envp[i] = NULL; + + return 0; +} + +static int sdio_bus_probe(struct device *dev) +{ + struct sdio_driver *drv = to_sdio_driver(dev->driver); + struct sdio_func *func = dev_to_sdio_func(dev); + const struct sdio_device_id *id; + int ret; + + id = sdio_match_device(func, drv); + if (!id) + return -ENODEV; + + /* Set the default block size so the driver is sure it's something + * sensible. */ + sdio_claim_host(func); + ret = sdio_set_block_size(func, 0); + sdio_release_host(func); + if (ret) + return ret; + + return drv->probe(func, id); +} + +static int sdio_bus_remove(struct device *dev) +{ + struct sdio_driver *drv = to_sdio_driver(dev->driver); + struct sdio_func *func = dev_to_sdio_func(dev); + + drv->remove(func); + + if (func->irq_handler) { + printk(KERN_WARNING "WARNING: driver %s did not remove " + "its interrupt handler!\n", drv->name); + sdio_claim_host(func); + sdio_release_irq(func); + sdio_release_host(func); + } + + return 0; +} + +static struct bus_type sdio_bus_type = { + .name = "sdio", + .dev_attrs = sdio_dev_attrs, + .match = sdio_bus_match, + .uevent = sdio_bus_uevent, + .probe = sdio_bus_probe, + .remove = sdio_bus_remove, +}; + +int sdio_register_bus(void) +{ + return bus_register(&sdio_bus_type); +} + +void sdio_unregister_bus(void) +{ + bus_unregister(&sdio_bus_type); +} + +/** + * sdio_register_driver - register a function driver + * @drv: SDIO function driver + */ +int sdio_register_driver(struct sdio_driver *drv) +{ + drv->drv.name = drv->name; + drv->drv.bus = &sdio_bus_type; + return driver_register(&drv->drv); +} + +EXPORT_SYMBOL_GPL(sdio_register_driver); + +/** + * sdio_unregister_driver - unregister a function driver + * @drv: SDIO function driver + */ +void sdio_unregister_driver(struct sdio_driver *drv) +{ + drv->drv.bus = &sdio_bus_type; + driver_unregister(&drv->drv); +} + +EXPORT_SYMBOL_GPL(sdio_unregister_driver); + +static void sdio_release_func(struct device *dev) +{ + struct sdio_func *func = dev_to_sdio_func(dev); + + sdio_free_func_cis(func); + + kfree(func); +} + +/* + * Allocate and initialise a new SDIO function structure. + */ +struct sdio_func *sdio_alloc_func(struct mmc_card *card) +{ + struct sdio_func *func; + + func = kzalloc(sizeof(struct sdio_func), GFP_KERNEL); + if (!func) + return ERR_PTR(-ENOMEM); + + func->card = card; + + device_initialize(&func->dev); + + func->dev.parent = &card->dev; + func->dev.bus = &sdio_bus_type; + func->dev.release = sdio_release_func; + + return func; +} + +/* + * Register a new SDIO function with the driver model. + */ +int sdio_add_func(struct sdio_func *func) +{ + int ret; + + snprintf(func->dev.bus_id, sizeof(func->dev.bus_id), + "%s:%d", mmc_card_id(func->card), func->num); + + ret = device_add(&func->dev); + if (ret == 0) + sdio_func_set_present(func); + + return ret; +} + +/* + * Unregister a SDIO function with the driver model, and + * (eventually) free it. + */ +void sdio_remove_func(struct sdio_func *func) +{ + if (sdio_func_present(func)) + device_del(&func->dev); + + put_device(&func->dev); +} + diff --git a/drivers/mmc/core/sdio_bus.h b/drivers/mmc/core/sdio_bus.h new file mode 100644 index 000000000000..567a76821ba7 --- /dev/null +++ b/drivers/mmc/core/sdio_bus.h @@ -0,0 +1,22 @@ +/* + * linux/drivers/mmc/core/sdio_bus.h + * + * Copyright 2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ +#ifndef _MMC_CORE_SDIO_BUS_H +#define _MMC_CORE_SDIO_BUS_H + +struct sdio_func *sdio_alloc_func(struct mmc_card *card); +int sdio_add_func(struct sdio_func *func); +void sdio_remove_func(struct sdio_func *func); + +int sdio_register_bus(void); +void sdio_unregister_bus(void); + +#endif + diff --git a/drivers/mmc/core/sdio_cis.c b/drivers/mmc/core/sdio_cis.c new file mode 100644 index 000000000000..f655cc681e67 --- /dev/null +++ b/drivers/mmc/core/sdio_cis.c @@ -0,0 +1,404 @@ +/* + * linux/drivers/mmc/core/sdio_cis.c + * + * Author: Nicolas Pitre + * Created: June 11, 2007 + * Copyright: MontaVista Software Inc. + * + * Copyright 2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#include <linux/kernel.h> + +#include <linux/mmc/host.h> +#include <linux/mmc/card.h> +#include <linux/mmc/sdio.h> +#include <linux/mmc/sdio_func.h> + +#include "sdio_cis.h" +#include "sdio_ops.h" + +#define sdio_card_func_id(card, func) \ + (func ? sdio_func_id(func) : mmc_card_id(card)) + +static int cistpl_vers_1(struct mmc_card *card, struct sdio_func *func, + const unsigned char *buf, unsigned size) +{ + unsigned i, nr_strings = 0; + + printk(KERN_DEBUG "%s: CISTPL_VERS_1 = %u.%u\n", + sdio_card_func_id(card, func), buf[0], buf[1]); + buf += 2; + + for (i = 0; i < size - 2; i++) { + if (buf[i] == 0xff) + break; + if (buf[i] == 0) + nr_strings++; + } + printk(KERN_INFO "%s:", sdio_card_func_id(card, func)); + for (i = 0; i < nr_strings; i++) { + printk(" \"%s\"", buf); + while (*buf++); + } + printk("\n"); + + return 0; +} + +static int cistpl_manfid(struct mmc_card *card, struct sdio_func *func, + const unsigned char *buf, unsigned size) +{ + unsigned int vendor, device; + + /* TPLMID_MANF */ + vendor = buf[0] | (buf[1] << 8); + printk(KERN_DEBUG "%s: TPLMID_MANF = 0x%04x\n", + sdio_card_func_id(card, func), vendor); + + /* TPLMID_CARD */ + device = buf[2] | (buf[3] << 8); + printk(KERN_DEBUG "%s: TPLMID_CARD = 0x%04x\n", + sdio_card_func_id(card, func), device); + + if (func) { + func->vendor = vendor; + func->device = device; + } else { + card->cis.vendor = vendor; + card->cis.device = device; + } + + return 0; +} + +static int cistpl_funcid(struct mmc_card *card, struct sdio_func *func, + const unsigned char *buf, unsigned size) +{ + /* TPLFID_FUNCTION */ + printk(KERN_DEBUG "%s: TPLFID_FUNCTION = 0x%02x\n", + sdio_card_func_id(card, func), buf[0]); + + return 0; +} + +static const unsigned char speed_val[16] = + { 0, 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80 }; +static const unsigned int speed_unit[8] = + { 10000, 100000, 1000000, 10000000, 0, 0, 0, 0 }; + +static int cistpl_funce_common(struct mmc_card *card, + const unsigned char *buf, unsigned size) +{ + if (size < 0x04 || buf[0] != 0) + return -EINVAL; + + /* TPLFE_FN0_BLK_SIZE */ + card->cis.blksize = buf[1] | (buf[2] << 8); + printk(KERN_DEBUG "%s: TPLFE_FN0_BLK_SIZE = %u\n", + mmc_card_id(card), (unsigned)card->cis.blksize); + /* TPLFE_MAX_TRAN_SPEED */ + card->cis.max_dtr = speed_val[(buf[3] >> 3) & 15] * + speed_unit[buf[3] & 7]; + printk(KERN_DEBUG "%s: max speed = %u kbps\n", + mmc_card_id(card), (unsigned)card->cis.max_dtr/1000); + + return 0; +} + +static int cistpl_funce_func(struct sdio_func *func, + const unsigned char *buf, unsigned size) +{ + unsigned val; + unsigned vsn; + unsigned min_size; + + vsn = func->card->cccr.sdio_vsn; + min_size = (vsn == SDIO_SDIO_REV_1_00) ? 28 : 42; + + if (size < min_size || buf[0] != 1) + return -EINVAL; + + /* TPLFE_FUNCTION_INFO */ + val = buf[1]; + printk(KERN_DEBUG "%s: TPLFE_FUNCTION_INFO = 0x%02x\n", + sdio_func_id(func), val); + /* TPLFE_STD_IO_REV */ + val = buf[2]; + printk(KERN_DEBUG "%s: TPLFE_STD_IO_REV = 0x%02x\n", + sdio_func_id(func), val); + /* TPLFE_CARD_PSN */ + val = buf[3] | (buf[4] << 8) | (buf[5] << 16) | (buf[6] << 24); + printk(KERN_DEBUG "%s: TPLFE_CARD_PSN = 0x%08x\n", + sdio_func_id(func), val); + /* TPLFE_CSA_SIZE */ + val = buf[7] | (buf[8] << 8) | (buf[9] << 16) | (buf[10] << 24); + printk(KERN_DEBUG "%s: TPLFE_CSA_SIZE = 0x%08x\n", + sdio_func_id(func), val); + /* TPLFE_CSA_PROPERTY */ + val = buf[11]; + printk(KERN_DEBUG "%s: TPLFE_CSA_PROPERTY = 0x%02x\n", + sdio_func_id(func), val); + /* TPLFE_MAX_BLK_SIZE */ + func->max_blksize = buf[12] | (buf[13] << 8); + printk(KERN_DEBUG "%s: TPLFE_MAX_BLK_SIZE = %u\n", + sdio_func_id(func), (unsigned)func->max_blksize); + /* TPLFE_OCR */ + val = buf[14] | (buf[15] << 8) | (buf[16] << 16) | (buf[17] << 24); + printk(KERN_DEBUG "%s: TPLFE_OCR = 0x%08x\n", + sdio_func_id(func), val); + /* TPLFE_OP_MIN_PWR, TPLFE_OP_AVG_PWR, TPLFE_OP_MAX_PWR */ + printk(KERN_DEBUG "%s: operating current (min/avg/max) = %u/%u/%u mA\n", + sdio_func_id(func), buf[18], buf[19], buf[20]); + /* TPLFE_SB_MIN_PWR, TPLFE_SB_AVG_PWR, TPLFE_SB_MAX_PWR */ + printk(KERN_DEBUG "%s: standby current (min/avg/max) = %u/%u/%u mA\n", + sdio_func_id(func), buf[21], buf[22], buf[23]); + /* TPLFE_MIN_BW */ + val = buf[24] | (buf[25] << 8); + printk(KERN_DEBUG "%s: minimum data bandwidth = %u KB/sec\n", + sdio_func_id(func), val); + /* TPLFE_OPT_BW */ + val = buf[26] | (buf[27] << 8); + printk(KERN_DEBUG "%s: optimum data bandwidth = %u KB/sec\n", + sdio_func_id(func), val); + if (vsn > SDIO_SDIO_REV_1_00) { + /* TPLFE_ENABLE_TIMEOUT_VAL */ + val = buf[28] | (buf[29] << 8); + printk(KERN_DEBUG "%s: TPLFE_ENABLE_TIMEOUT_VAL = %u\n", + sdio_func_id(func), val); + /* TPLFE_SP_AVG_PWR_3.3V */ + val = buf[30] | (buf[31] << 8); + /* TPLFE_SP_MAX_PWR_3.3V */ + val = buf[32] | (buf[33] << 8); + /* TPLFE_HP_AVG_PWR_3.3V */ + val = buf[34] | (buf[35] << 8); + /* TPLFE_HP_MAX_PWR_3.3V */ + val = buf[36] | (buf[37] << 8); + /* TPLFE_LP_AVG_PWR_3.3V */ + val = buf[38] | (buf[39] << 8); + /* TPLFE_LP_MAX_PWR_3.3V */ + val = buf[40] | (buf[41] << 8); + } + + return 0; +} + +static int cistpl_funce(struct mmc_card *card, struct sdio_func *func, + const unsigned char *buf, unsigned size) +{ + int ret; + + /* + * There should be two versions of the CISTPL_FUNCE tuple, + * one for the common CIS (function 0) and a version used by + * the individual function's CIS (1-7). Yet, the later has a + * different length depending on the SDIO spec version. + */ + if (func) + ret = cistpl_funce_func(func, buf, size); + else + ret = cistpl_funce_common(card, buf, size); + + if (ret) { + printk(KERN_ERR "%s: bad CISTPL_FUNCE size %u " + "type %u\n", sdio_card_func_id(card, func), + size, buf[0]); + return ret; + } + + return 0; +} + +typedef int (tpl_parse_t)(struct mmc_card *, struct sdio_func *, + const unsigned char *, unsigned); + +struct cis_tpl { + unsigned char code; + unsigned char min_size; + tpl_parse_t *parse; +}; + +static const struct cis_tpl cis_tpl_list[] = { + { 0x15, 3, cistpl_vers_1 }, + { 0x20, 4, cistpl_manfid }, + { 0x21, 2, cistpl_funcid }, + { 0x22, 0, cistpl_funce }, +}; + +static int sdio_read_cis(struct mmc_card *card, struct sdio_func *func) +{ + int ret; + struct sdio_func_tuple *this, **prev; + unsigned i, ptr = 0; + + /* + * Note that this works for the common CIS (function number 0) as + * well as a function's CIS * since SDIO_CCCR_CIS and SDIO_FBR_CIS + * have the same offset. + */ + for (i = 0; i < 3; i++) { + unsigned char x, fn; + + if (func) + fn = func->num; + else + fn = 0; + + ret = mmc_io_rw_direct(card, 0, 0, + SDIO_FBR_BASE(fn) + SDIO_FBR_CIS + i, 0, &x); + if (ret) + return ret; + ptr |= x << (i * 8); + } + + if (func) + prev = &func->tuples; + else + prev = &card->tuples; + + BUG_ON(*prev); + + do { + unsigned char tpl_code, tpl_link; + + ret = mmc_io_rw_direct(card, 0, 0, ptr++, 0, &tpl_code); + if (ret) + break; + + /* 0xff means we're done */ + if (tpl_code == 0xff) + break; + + ret = mmc_io_rw_direct(card, 0, 0, ptr++, 0, &tpl_link); + if (ret) + break; + + this = kmalloc(sizeof(*this) + tpl_link, GFP_KERNEL); + if (!this) + return -ENOMEM; + + for (i = 0; i < tpl_link; i++) { + ret = mmc_io_rw_direct(card, 0, 0, + ptr + i, 0, &this->data[i]); + if (ret) + break; + } + if (ret) { + kfree(this); + break; + } + + for (i = 0; i < ARRAY_SIZE(cis_tpl_list); i++) + if (cis_tpl_list[i].code == tpl_code) + break; + if (i >= ARRAY_SIZE(cis_tpl_list)) { + /* this tuple is unknown to the core */ + this->next = NULL; + this->code = tpl_code; + this->size = tpl_link; + *prev = this; + prev = &this->next; + printk(KERN_DEBUG + "%s: queuing CIS tuple 0x%02x length %u\n", + sdio_card_func_id(card, func), + tpl_code, tpl_link); + } else { + const struct cis_tpl *tpl = cis_tpl_list + i; + if (tpl_link < tpl->min_size) { + printk(KERN_ERR + "%s: bad CIS tuple 0x%02x (length = %u, expected >= %u\n", + sdio_card_func_id(card, func), + tpl_code, tpl_link, tpl->min_size); + ret = -EINVAL; + } else { + ret = tpl->parse(card, func, + this->data, tpl_link); + } + kfree(this); + } + + ptr += tpl_link; + } while (!ret); + + /* + * Link in all unknown tuples found in the common CIS so that + * drivers don't have to go digging in two places. + */ + if (func) + *prev = card->tuples; + + return ret; +} + +int sdio_read_common_cis(struct mmc_card *card) +{ + return sdio_read_cis(card, NULL); +} + +void sdio_free_common_cis(struct mmc_card *card) +{ + struct sdio_func_tuple *tuple, *victim; + + tuple = card->tuples; + + while (tuple) { + victim = tuple; + tuple = tuple->next; + kfree(victim); + } + + card->tuples = NULL; +} + +int sdio_read_func_cis(struct sdio_func *func) +{ + int ret; + + ret = sdio_read_cis(func->card, func); + if (ret) + return ret; + + /* + * Since we've linked to tuples in the card structure, + * we must make sure we have a reference to it. + */ + get_device(&func->card->dev); + + /* + * Vendor/device id is optional for function CIS, so + * copy it from the card structure as needed. + */ + if (func->vendor == 0) { + func->vendor = func->card->cis.vendor; + func->device = func->card->cis.device; + } + + return 0; +} + +void sdio_free_func_cis(struct sdio_func *func) +{ + struct sdio_func_tuple *tuple, *victim; + + tuple = func->tuples; + + while (tuple && tuple != func->card->tuples) { + victim = tuple; + tuple = tuple->next; + kfree(victim); + } + + func->tuples = NULL; + + /* + * We have now removed the link to the tuples in the + * card structure, so remove the reference. + */ + put_device(&func->card->dev); +} + diff --git a/drivers/mmc/core/sdio_cis.h b/drivers/mmc/core/sdio_cis.h new file mode 100644 index 000000000000..4d903c2e425e --- /dev/null +++ b/drivers/mmc/core/sdio_cis.h @@ -0,0 +1,23 @@ +/* + * linux/drivers/mmc/core/sdio_cis.h + * + * Author: Nicolas Pitre + * Created: June 11, 2007 + * Copyright: MontaVista Software Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#ifndef _MMC_SDIO_CIS_H +#define _MMC_SDIO_CIS_H + +int sdio_read_common_cis(struct mmc_card *card); +void sdio_free_common_cis(struct mmc_card *card); + +int sdio_read_func_cis(struct sdio_func *func); +void sdio_free_func_cis(struct sdio_func *func); + +#endif diff --git a/drivers/mmc/core/sdio_io.c b/drivers/mmc/core/sdio_io.c new file mode 100644 index 000000000000..60d7404ae66b --- /dev/null +++ b/drivers/mmc/core/sdio_io.c @@ -0,0 +1,561 @@ +/* + * linux/drivers/mmc/core/sdio_io.c + * + * Copyright 2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#include <linux/mmc/host.h> +#include <linux/mmc/card.h> +#include <linux/mmc/sdio.h> +#include <linux/mmc/sdio_func.h> + +#include "sdio_ops.h" + +/** + * sdio_claim_host - exclusively claim a bus for a certain SDIO function + * @func: SDIO function that will be accessed + * + * Claim a bus for a set of operations. The SDIO function given + * is used to figure out which bus is relevant. + */ +void sdio_claim_host(struct sdio_func *func) +{ + BUG_ON(!func); + BUG_ON(!func->card); + + mmc_claim_host(func->card->host); +} + +EXPORT_SYMBOL_GPL(sdio_claim_host); + +/** + * sdio_release_host - release a bus for a certain SDIO function + * @func: SDIO function that was accessed + * + * Release a bus, allowing others to claim the bus for their + * operations. + */ +void sdio_release_host(struct sdio_func *func) +{ + BUG_ON(!func); + BUG_ON(!func->card); + + mmc_release_host(func->card->host); +} + +EXPORT_SYMBOL_GPL(sdio_release_host); + +/** + * sdio_enable_func - enables a SDIO function for usage + * @func: SDIO function to enable + * + * Powers up and activates a SDIO function so that register + * access is possible. + */ +int sdio_enable_func(struct sdio_func *func) +{ + int ret; + unsigned char reg; + unsigned long timeout; + + BUG_ON(!func); + BUG_ON(!func->card); + + pr_debug("SDIO: Enabling device %s...\n", sdio_func_id(func)); + + ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IOEx, 0, ®); + if (ret) + goto err; + + reg |= 1 << func->num; + + ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IOEx, reg, NULL); + if (ret) + goto err; + + /* + * FIXME: This should timeout based on information in the CIS, + * but we don't have card to parse that yet. + */ + timeout = jiffies + HZ; + + while (1) { + ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IORx, 0, ®); + if (ret) + goto err; + if (reg & (1 << func->num)) + break; + ret = -ETIME; + if (time_after(jiffies, timeout)) + goto err; + } + + pr_debug("SDIO: Enabled device %s\n", sdio_func_id(func)); + + return 0; + +err: + pr_debug("SDIO: Failed to enable device %s\n", sdio_func_id(func)); + return ret; +} + +EXPORT_SYMBOL_GPL(sdio_enable_func); + +/** + * sdio_disable_func - disable a SDIO function + * @func: SDIO function to disable + * + * Powers down and deactivates a SDIO function. Register access + * to this function will fail until the function is reenabled. + */ +int sdio_disable_func(struct sdio_func *func) +{ + int ret; + unsigned char reg; + + BUG_ON(!func); + BUG_ON(!func->card); + + pr_debug("SDIO: Disabling device %s...\n", sdio_func_id(func)); + + ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IOEx, 0, ®); + if (ret) + goto err; + + reg &= ~(1 << func->num); + + ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IOEx, reg, NULL); + if (ret) + goto err; + + pr_debug("SDIO: Disabled device %s\n", sdio_func_id(func)); + + return 0; + +err: + pr_debug("SDIO: Failed to disable device %s\n", sdio_func_id(func)); + return -EIO; +} + +EXPORT_SYMBOL_GPL(sdio_disable_func); + +/** + * sdio_set_block_size - set the block size of an SDIO function + * @func: SDIO function to change + * @blksz: new block size or 0 to use the default. + * + * The default block size is the largest supported by both the function + * and the host, with a maximum of 512 to ensure that arbitrarily sized + * data transfer use the optimal (least) number of commands. + * + * A driver may call this to override the default block size set by the + * core. This can be used to set a block size greater than the maximum + * that reported by the card; it is the driver's responsibility to ensure + * it uses a value that the card supports. + * + * Returns 0 on success, -EINVAL if the host does not support the + * requested block size, or -EIO (etc.) if one of the resultant FBR block + * size register writes failed. + * + */ +int sdio_set_block_size(struct sdio_func *func, unsigned blksz) +{ + int ret; + + if (blksz > func->card->host->max_blk_size) + return -EINVAL; + + if (blksz == 0) { + blksz = min(min( + func->max_blksize, + func->card->host->max_blk_size), + 512u); + } + + ret = mmc_io_rw_direct(func->card, 1, 0, + SDIO_FBR_BASE(func->num) + SDIO_FBR_BLKSIZE, + blksz & 0xff, NULL); + if (ret) + return ret; + ret = mmc_io_rw_direct(func->card, 1, 0, + SDIO_FBR_BASE(func->num) + SDIO_FBR_BLKSIZE + 1, + (blksz >> 8) & 0xff, NULL); + if (ret) + return ret; + func->cur_blksize = blksz; + return 0; +} + +EXPORT_SYMBOL_GPL(sdio_set_block_size); + +/* Split an arbitrarily sized data transfer into several + * IO_RW_EXTENDED commands. */ +static int sdio_io_rw_ext_helper(struct sdio_func *func, int write, + unsigned addr, int incr_addr, u8 *buf, unsigned size) +{ + unsigned remainder = size; + unsigned max_blocks; + int ret; + + /* Do the bulk of the transfer using block mode (if supported). */ + if (func->card->cccr.multi_block) { + /* Blocks per command is limited by host count, host transfer + * size (we only use a single sg entry) and the maximum for + * IO_RW_EXTENDED of 511 blocks. */ + max_blocks = min(min( + func->card->host->max_blk_count, + func->card->host->max_seg_size / func->cur_blksize), + 511u); + + while (remainder > func->cur_blksize) { + unsigned blocks; + + blocks = remainder / func->cur_blksize; + if (blocks > max_blocks) + blocks = max_blocks; + size = blocks * func->cur_blksize; + + ret = mmc_io_rw_extended(func->card, write, + func->num, addr, incr_addr, buf, + blocks, func->cur_blksize); + if (ret) + return ret; + + remainder -= size; + buf += size; + if (incr_addr) + addr += size; + } + } + + /* Write the remainder using byte mode. */ + while (remainder > 0) { + size = remainder; + if (size > func->cur_blksize) + size = func->cur_blksize; + if (size > 512) + size = 512; /* maximum size for byte mode */ + + ret = mmc_io_rw_extended(func->card, write, func->num, addr, + incr_addr, buf, 1, size); + if (ret) + return ret; + + remainder -= size; + buf += size; + if (incr_addr) + addr += size; + } + return 0; +} + +/** + * sdio_readb - read a single byte from a SDIO function + * @func: SDIO function to access + * @addr: address to read + * @err_ret: optional status value from transfer + * + * Reads a single byte from the address space of a given SDIO + * function. If there is a problem reading the address, 0xff + * is returned and @err_ret will contain the error code. + */ +unsigned char sdio_readb(struct sdio_func *func, unsigned int addr, + int *err_ret) +{ + int ret; + unsigned char val; + + BUG_ON(!func); + + if (err_ret) + *err_ret = 0; + + ret = mmc_io_rw_direct(func->card, 0, func->num, addr, 0, &val); + if (ret) { + if (err_ret) + *err_ret = ret; + return 0xFF; + } + + return val; +} + +EXPORT_SYMBOL_GPL(sdio_readb); + +/** + * sdio_writeb - write a single byte to a SDIO function + * @func: SDIO function to access + * @b: byte to write + * @addr: address to write to + * @err_ret: optional status value from transfer + * + * Writes a single byte to the address space of a given SDIO + * function. @err_ret will contain the status of the actual + * transfer. + */ +void sdio_writeb(struct sdio_func *func, unsigned char b, unsigned int addr, + int *err_ret) +{ + int ret; + + BUG_ON(!func); + + ret = mmc_io_rw_direct(func->card, 1, func->num, addr, b, NULL); + if (err_ret) + *err_ret = ret; +} + +EXPORT_SYMBOL_GPL(sdio_writeb); + +/** + * sdio_memcpy_fromio - read a chunk of memory from a SDIO function + * @func: SDIO function to access + * @dst: buffer to store the data + * @addr: address to begin reading from + * @count: number of bytes to read + * + * Reads from the address space of a given SDIO function. Return + * value indicates if the transfer succeeded or not. + */ +int sdio_memcpy_fromio(struct sdio_func *func, void *dst, + unsigned int addr, int count) +{ + return sdio_io_rw_ext_helper(func, 0, addr, 1, dst, count); +} + +EXPORT_SYMBOL_GPL(sdio_memcpy_fromio); + +/** + * sdio_memcpy_toio - write a chunk of memory to a SDIO function + * @func: SDIO function to access + * @addr: address to start writing to + * @src: buffer that contains the data to write + * @count: number of bytes to write + * + * Writes to the address space of a given SDIO function. Return + * value indicates if the transfer succeeded or not. + */ +int sdio_memcpy_toio(struct sdio_func *func, unsigned int addr, + void *src, int count) +{ + return sdio_io_rw_ext_helper(func, 1, addr, 1, src, count); +} + +/** + * sdio_readsb - read from a FIFO on a SDIO function + * @func: SDIO function to access + * @dst: buffer to store the data + * @addr: address of (single byte) FIFO + * @count: number of bytes to read + * + * Reads from the specified FIFO of a given SDIO function. Return + * value indicates if the transfer succeeded or not. + */ +int sdio_readsb(struct sdio_func *func, void *dst, unsigned int addr, + int count) +{ + return sdio_io_rw_ext_helper(func, 0, addr, 0, dst, count); +} + +EXPORT_SYMBOL_GPL(sdio_readsb); + +/** + * sdio_writesb - write to a FIFO of a SDIO function + * @func: SDIO function to access + * @addr: address of (single byte) FIFO + * @src: buffer that contains the data to write + * @count: number of bytes to write + * + * Writes to the specified FIFO of a given SDIO function. Return + * value indicates if the transfer succeeded or not. + */ +int sdio_writesb(struct sdio_func *func, unsigned int addr, void *src, + int count) +{ + return sdio_io_rw_ext_helper(func, 1, addr, 0, src, count); +} + +EXPORT_SYMBOL_GPL(sdio_writesb); + +/** + * sdio_readw - read a 16 bit integer from a SDIO function + * @func: SDIO function to access + * @addr: address to read + * @err_ret: optional status value from transfer + * + * Reads a 16 bit integer from the address space of a given SDIO + * function. If there is a problem reading the address, 0xffff + * is returned and @err_ret will contain the error code. + */ +unsigned short sdio_readw(struct sdio_func *func, unsigned int addr, + int *err_ret) +{ + int ret; + + if (err_ret) + *err_ret = 0; + + ret = sdio_memcpy_fromio(func, func->tmpbuf, addr, 2); + if (ret) { + if (err_ret) + *err_ret = ret; + return 0xFFFF; + } + + return le16_to_cpu(*(u16*)func->tmpbuf); +} + +EXPORT_SYMBOL_GPL(sdio_readw); + +/** + * sdio_writew - write a 16 bit integer to a SDIO function + * @func: SDIO function to access + * @b: integer to write + * @addr: address to write to + * @err_ret: optional status value from transfer + * + * Writes a 16 bit integer to the address space of a given SDIO + * function. @err_ret will contain the status of the actual + * transfer. + */ +void sdio_writew(struct sdio_func *func, unsigned short b, unsigned int addr, + int *err_ret) +{ + int ret; + + *(u16*)func->tmpbuf = cpu_to_le16(b); + + ret = sdio_memcpy_toio(func, addr, func->tmpbuf, 2); + if (err_ret) + *err_ret = ret; +} + +EXPORT_SYMBOL_GPL(sdio_writew); + +/** + * sdio_readl - read a 32 bit integer from a SDIO function + * @func: SDIO function to access + * @addr: address to read + * @err_ret: optional status value from transfer + * + * Reads a 32 bit integer from the address space of a given SDIO + * function. If there is a problem reading the address, + * 0xffffffff is returned and @err_ret will contain the error + * code. + */ +unsigned long sdio_readl(struct sdio_func *func, unsigned int addr, + int *err_ret) +{ + int ret; + + if (err_ret) + *err_ret = 0; + + ret = sdio_memcpy_fromio(func, func->tmpbuf, addr, 4); + if (ret) { + if (err_ret) + *err_ret = ret; + return 0xFFFFFFFF; + } + + return le32_to_cpu(*(u32*)func->tmpbuf); +} + +EXPORT_SYMBOL_GPL(sdio_readl); + +/** + * sdio_writel - write a 32 bit integer to a SDIO function + * @func: SDIO function to access + * @b: integer to write + * @addr: address to write to + * @err_ret: optional status value from transfer + * + * Writes a 32 bit integer to the address space of a given SDIO + * function. @err_ret will contain the status of the actual + * transfer. + */ +void sdio_writel(struct sdio_func *func, unsigned long b, unsigned int addr, + int *err_ret) +{ + int ret; + + *(u32*)func->tmpbuf = cpu_to_le32(b); + + ret = sdio_memcpy_toio(func, addr, func->tmpbuf, 4); + if (err_ret) + *err_ret = ret; +} + +EXPORT_SYMBOL_GPL(sdio_writel); + +/** + * sdio_f0_readb - read a single byte from SDIO function 0 + * @func: an SDIO function of the card + * @addr: address to read + * @err_ret: optional status value from transfer + * + * Reads a single byte from the address space of SDIO function 0. + * If there is a problem reading the address, 0xff is returned + * and @err_ret will contain the error code. + */ +unsigned char sdio_f0_readb(struct sdio_func *func, unsigned int addr, + int *err_ret) +{ + int ret; + unsigned char val; + + BUG_ON(!func); + + if (err_ret) + *err_ret = 0; + + ret = mmc_io_rw_direct(func->card, 0, 0, addr, 0, &val); + if (ret) { + if (err_ret) + *err_ret = ret; + return 0xFF; + } + + return val; +} + +EXPORT_SYMBOL_GPL(sdio_f0_readb); + +/** + * sdio_f0_writeb - write a single byte to SDIO function 0 + * @func: an SDIO function of the card + * @b: byte to write + * @addr: address to write to + * @err_ret: optional status value from transfer + * + * Writes a single byte to the address space of SDIO function 0. + * @err_ret will contain the status of the actual transfer. + * + * Only writes to the vendor specific CCCR registers (0xF0 - + * 0xFF) are permiited; @err_ret will be set to -EINVAL for * + * writes outside this range. + */ +void sdio_f0_writeb(struct sdio_func *func, unsigned char b, unsigned int addr, + int *err_ret) +{ + int ret; + + BUG_ON(!func); + + if (addr < 0xF0 || addr > 0xFF) { + if (err_ret) + *err_ret = -EINVAL; + return; + } + + ret = mmc_io_rw_direct(func->card, 1, 0, addr, b, NULL); + if (err_ret) + *err_ret = ret; +} + +EXPORT_SYMBOL_GPL(sdio_f0_writeb); diff --git a/drivers/mmc/core/sdio_irq.c b/drivers/mmc/core/sdio_irq.c new file mode 100644 index 000000000000..337ac47f5202 --- /dev/null +++ b/drivers/mmc/core/sdio_irq.c @@ -0,0 +1,245 @@ +/* + * linux/drivers/mmc/core/sdio_irq.c + * + * Author: Nicolas Pitre + * Created: June 18, 2007 + * Copyright: MontaVista Software Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/kthread.h> +#include <linux/wait.h> +#include <linux/delay.h> + +#include <linux/mmc/core.h> +#include <linux/mmc/host.h> +#include <linux/mmc/card.h> +#include <linux/mmc/sdio.h> +#include <linux/mmc/sdio_func.h> + +#include "sdio_ops.h" + +static int process_sdio_pending_irqs(struct mmc_card *card) +{ + int i, ret; + unsigned char pending; + + ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_INTx, 0, &pending); + if (ret) { + printk(KERN_DEBUG "%s: error %d reading SDIO_CCCR_INTx\n", + mmc_card_id(card), ret); + return ret; + } + + for (i = 1; i <= 7; i++) { + if (pending & (1 << i)) { + struct sdio_func *func = card->sdio_func[i - 1]; + if (!func) { + printk(KERN_WARNING "%s: pending IRQ for " + "non-existant function\n", + sdio_func_id(func)); + } else if (func->irq_handler) { + func->irq_handler(func); + } else + printk(KERN_WARNING "%s: pending IRQ with no handler\n", + sdio_func_id(func)); + } + } + + return 0; +} + +static int sdio_irq_thread(void *_host) +{ + struct mmc_host *host = _host; + struct sched_param param = { .sched_priority = 1 }; + unsigned long period; + int ret; + + sched_setscheduler(current, SCHED_FIFO, ¶m); + + /* + * We want to allow for SDIO cards to work even on non SDIO + * aware hosts. One thing that non SDIO host cannot do is + * asynchronous notification of pending SDIO card interrupts + * hence we poll for them in that case. + */ + period = (host->caps & MMC_CAP_SDIO_IRQ) ? + MAX_SCHEDULE_TIMEOUT : msecs_to_jiffies(10); + + pr_debug("%s: IRQ thread started (poll period = %lu jiffies)\n", + mmc_hostname(host), period); + + do { + /* + * We claim the host here on drivers behalf for a couple + * reasons: + * + * 1) it is already needed to retrieve the CCCR_INTx; + * 2) we want the driver(s) to clear the IRQ condition ASAP; + * 3) we need to control the abort condition locally. + * + * Just like traditional hard IRQ handlers, we expect SDIO + * IRQ handlers to be quick and to the point, so that the + * holding of the host lock does not cover too much work + * that doesn't require that lock to be held. + */ + ret = __mmc_claim_host(host, &host->sdio_irq_thread_abort); + if (ret) + break; + ret = process_sdio_pending_irqs(host->card); + mmc_release_host(host); + + /* + * Give other threads a chance to run in the presence of + * errors. FIXME: determine if due to card removal and + * possibly exit this thread if so. + */ + if (ret) + ssleep(1); + + set_task_state(current, TASK_INTERRUPTIBLE); + if (host->caps & MMC_CAP_SDIO_IRQ) + host->ops->enable_sdio_irq(host, 1); + if (!kthread_should_stop()) + schedule_timeout(period); + set_task_state(current, TASK_RUNNING); + } while (!kthread_should_stop()); + + if (host->caps & MMC_CAP_SDIO_IRQ) + host->ops->enable_sdio_irq(host, 0); + + pr_debug("%s: IRQ thread exiting with code %d\n", + mmc_hostname(host), ret); + + return ret; +} + +static int sdio_card_irq_get(struct mmc_card *card) +{ + struct mmc_host *host = card->host; + + WARN_ON(!host->claimed); + + if (!host->sdio_irqs++) { + atomic_set(&host->sdio_irq_thread_abort, 0); + host->sdio_irq_thread = + kthread_run(sdio_irq_thread, host, "ksdiorqd"); + if (IS_ERR(host->sdio_irq_thread)) { + int err = PTR_ERR(host->sdio_irq_thread); + host->sdio_irqs--; + return err; + } + } + + return 0; +} + +static int sdio_card_irq_put(struct mmc_card *card) +{ + struct mmc_host *host = card->host; + + WARN_ON(!host->claimed); + BUG_ON(host->sdio_irqs < 1); + + if (!--host->sdio_irqs) { + atomic_set(&host->sdio_irq_thread_abort, 1); + kthread_stop(host->sdio_irq_thread); + } + + return 0; +} + +/** + * sdio_claim_irq - claim the IRQ for a SDIO function + * @func: SDIO function + * @handler: IRQ handler callback + * + * Claim and activate the IRQ for the given SDIO function. The provided + * handler will be called when that IRQ is asserted. The host is always + * claimed already when the handler is called so the handler must not + * call sdio_claim_host() nor sdio_release_host(). + */ +int sdio_claim_irq(struct sdio_func *func, sdio_irq_handler_t *handler) +{ + int ret; + unsigned char reg; + + BUG_ON(!func); + BUG_ON(!func->card); + + pr_debug("SDIO: Enabling IRQ for %s...\n", sdio_func_id(func)); + + if (func->irq_handler) { + pr_debug("SDIO: IRQ for %s already in use.\n", sdio_func_id(func)); + return -EBUSY; + } + + ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IENx, 0, ®); + if (ret) + return ret; + + reg |= 1 << func->num; + + reg |= 1; /* Master interrupt enable */ + + ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, reg, NULL); + if (ret) + return ret; + + func->irq_handler = handler; + ret = sdio_card_irq_get(func->card); + if (ret) + func->irq_handler = NULL; + + return ret; +} + +EXPORT_SYMBOL_GPL(sdio_claim_irq); + +/** + * sdio_release_irq - release the IRQ for a SDIO function + * @func: SDIO function + * + * Disable and release the IRQ for the given SDIO function. + */ +int sdio_release_irq(struct sdio_func *func) +{ + int ret; + unsigned char reg; + + BUG_ON(!func); + BUG_ON(!func->card); + + pr_debug("SDIO: Disabling IRQ for %s...\n", sdio_func_id(func)); + + if (func->irq_handler) { + func->irq_handler = NULL; + sdio_card_irq_put(func->card); + } + + ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IENx, 0, ®); + if (ret) + return ret; + + reg &= ~(1 << func->num); + + /* Disable master interrupt with the last function interrupt */ + if (!(reg & 0xFE)) + reg = 0; + + ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, reg, NULL); + if (ret) + return ret; + + return 0; +} + +EXPORT_SYMBOL_GPL(sdio_release_irq); + diff --git a/drivers/mmc/core/sdio_ops.c b/drivers/mmc/core/sdio_ops.c new file mode 100644 index 000000000000..a256c468b7eb --- /dev/null +++ b/drivers/mmc/core/sdio_ops.c @@ -0,0 +1,148 @@ +/* + * linux/drivers/mmc/sdio_ops.c + * + * Copyright 2006-2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#include <asm/scatterlist.h> +#include <linux/scatterlist.h> + +#include <linux/mmc/host.h> +#include <linux/mmc/card.h> +#include <linux/mmc/mmc.h> +#include <linux/mmc/sdio.h> + +#include "core.h" + +int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr) +{ + struct mmc_command cmd; + int i, err = 0; + + BUG_ON(!host); + + memset(&cmd, 0, sizeof(struct mmc_command)); + + cmd.opcode = SD_IO_SEND_OP_COND; + cmd.arg = ocr; + cmd.flags = MMC_RSP_R4 | MMC_CMD_BCR; + + for (i = 100; i; i--) { + err = mmc_wait_for_cmd(host, &cmd, MMC_CMD_RETRIES); + if (err) + break; + + if (cmd.resp[0] & MMC_CARD_BUSY || ocr == 0) + break; + + err = -ETIMEDOUT; + + mmc_delay(10); + } + + if (rocr) + *rocr = cmd.resp[0]; + + return err; +} + +int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn, + unsigned addr, u8 in, u8* out) +{ + struct mmc_command cmd; + int err; + + BUG_ON(!card); + BUG_ON(fn > 7); + + memset(&cmd, 0, sizeof(struct mmc_command)); + + cmd.opcode = SD_IO_RW_DIRECT; + cmd.arg = write ? 0x80000000 : 0x00000000; + cmd.arg |= fn << 28; + cmd.arg |= (write && out) ? 0x08000000 : 0x00000000; + cmd.arg |= addr << 9; + cmd.arg |= in; + cmd.flags = MMC_RSP_R5 | MMC_CMD_AC; + + err = mmc_wait_for_cmd(card->host, &cmd, 0); + if (err) + return err; + + if (cmd.resp[0] & R5_ERROR) + return -EIO; + if (cmd.resp[0] & R5_FUNCTION_NUMBER) + return -EINVAL; + if (cmd.resp[0] & R5_OUT_OF_RANGE) + return -ERANGE; + + if (out) + *out = cmd.resp[0] & 0xFF; + + return 0; +} + +int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn, + unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz) +{ + struct mmc_request mrq; + struct mmc_command cmd; + struct mmc_data data; + struct scatterlist sg; + + BUG_ON(!card); + BUG_ON(fn > 7); + BUG_ON(blocks == 1 && blksz > 512); + WARN_ON(blocks == 0); + WARN_ON(blksz == 0); + + memset(&mrq, 0, sizeof(struct mmc_request)); + memset(&cmd, 0, sizeof(struct mmc_command)); + memset(&data, 0, sizeof(struct mmc_data)); + + mrq.cmd = &cmd; + mrq.data = &data; + + cmd.opcode = SD_IO_RW_EXTENDED; + cmd.arg = write ? 0x80000000 : 0x00000000; + cmd.arg |= fn << 28; + cmd.arg |= incr_addr ? 0x04000000 : 0x00000000; + cmd.arg |= addr << 9; + if (blocks == 1 && blksz <= 512) + cmd.arg |= (blksz == 512) ? 0 : blksz; /* byte mode */ + else + cmd.arg |= 0x08000000 | blocks; /* block mode */ + cmd.flags = MMC_RSP_R5 | MMC_CMD_ADTC; + + data.blksz = blksz; + data.blocks = blocks; + data.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ; + data.sg = &sg; + data.sg_len = 1; + + sg_init_one(&sg, buf, blksz * blocks); + + mmc_set_data_timeout(&data, card, 0); + + mmc_wait_for_req(card->host, &mrq); + + if (cmd.error) + return cmd.error; + if (data.error) + return data.error; + + if (cmd.resp[0] & R5_ERROR) + return -EIO; + if (cmd.resp[0] & R5_FUNCTION_NUMBER) + return -EINVAL; + if (cmd.resp[0] & R5_OUT_OF_RANGE) + return -ERANGE; + + return 0; +} + diff --git a/drivers/mmc/core/sdio_ops.h b/drivers/mmc/core/sdio_ops.h new file mode 100644 index 000000000000..e2e74b0d17d8 --- /dev/null +++ b/drivers/mmc/core/sdio_ops.h @@ -0,0 +1,22 @@ +/* + * linux/drivers/mmc/sdio_ops.c + * + * Copyright 2006-2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#ifndef _MMC_SDIO_OPS_H +#define _MMC_SDIO_OPS_H + +int mmc_send_io_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr); +int mmc_io_rw_direct(struct mmc_card *card, int write, unsigned fn, + unsigned addr, u8 in, u8* out); +int mmc_io_rw_extended(struct mmc_card *card, int write, unsigned fn, + unsigned addr, int incr_addr, u8 *buf, unsigned blocks, unsigned blksz); + +#endif + diff --git a/drivers/mmc/host/at91_mci.c b/drivers/mmc/host/at91_mci.c index 62564ccde03a..4dab3dfa00ae 100644 --- a/drivers/mmc/host/at91_mci.c +++ b/drivers/mmc/host/at91_mci.c @@ -577,24 +577,22 @@ static void at91_mci_completed_command(struct at91mci_host *host) AT91_MCI_RENDE | AT91_MCI_RTOE | AT91_MCI_DCRCE | AT91_MCI_DTOE | AT91_MCI_OVRE | AT91_MCI_UNRE)) { if ((status & AT91_MCI_RCRCE) && !(mmc_resp_type(cmd) & MMC_RSP_CRC)) { - cmd->error = MMC_ERR_NONE; + cmd->error = 0; } else { if (status & (AT91_MCI_RTOE | AT91_MCI_DTOE)) - cmd->error = MMC_ERR_TIMEOUT; + cmd->error = -ETIMEDOUT; else if (status & (AT91_MCI_RCRCE | AT91_MCI_DCRCE)) - cmd->error = MMC_ERR_BADCRC; - else if (status & (AT91_MCI_OVRE | AT91_MCI_UNRE)) - cmd->error = MMC_ERR_FIFO; + cmd->error = -EILSEQ; else - cmd->error = MMC_ERR_FAILED; + cmd->error = -EIO; pr_debug("Error detected and set to %d (cmd = %d, retries = %d)\n", cmd->error, cmd->opcode, cmd->retries); } } else - cmd->error = MMC_ERR_NONE; + cmd->error = 0; at91_mci_process_next(host); } diff --git a/drivers/mmc/host/au1xmmc.c b/drivers/mmc/host/au1xmmc.c index 34c99d4ea041..49b0367e57c8 100644 --- a/drivers/mmc/host/au1xmmc.c +++ b/drivers/mmc/host/au1xmmc.c @@ -208,7 +208,7 @@ static int au1xmmc_send_command(struct au1xmmc_host *host, int wait, default: printk(KERN_INFO "au1xmmc: unhandled response type %02x\n", mmc_resp_type(cmd)); - return MMC_ERR_INVALID; + return -EINVAL; } if (flags & MMC_DATA_READ) { @@ -253,7 +253,7 @@ static int au1xmmc_send_command(struct au1xmmc_host *host, int wait, IRQ_ON(host, SD_CONFIG_CR); } - return MMC_ERR_NONE; + return 0; } static void au1xmmc_data_complete(struct au1xmmc_host *host, u32 status) @@ -278,7 +278,7 @@ static void au1xmmc_data_complete(struct au1xmmc_host *host, u32 status) while((host->flags & HOST_F_XMIT) && (status & SD_STATUS_DB)) status = au_readl(HOST_STATUS(host)); - data->error = MMC_ERR_NONE; + data->error = 0; dma_unmap_sg(mmc_dev(host->mmc), data->sg, data->sg_len, host->dma.dir); /* Process any errors */ @@ -288,14 +288,14 @@ static void au1xmmc_data_complete(struct au1xmmc_host *host, u32 status) crc |= ((status & 0x07) == 0x02) ? 0 : 1; if (crc) - data->error = MMC_ERR_BADCRC; + data->error = -EILSEQ; /* Clear the CRC bits */ au_writel(SD_STATUS_WC | SD_STATUS_RC, HOST_STATUS(host)); data->bytes_xfered = 0; - if (data->error == MMC_ERR_NONE) { + if (!data->error) { if (host->flags & HOST_F_DMA) { u32 chan = DMA_CHANNEL(host); @@ -475,7 +475,7 @@ static void au1xmmc_cmd_complete(struct au1xmmc_host *host, u32 status) return; cmd = mrq->cmd; - cmd->error = MMC_ERR_NONE; + cmd->error = 0; if (cmd->flags & MMC_RSP_PRESENT) { if (cmd->flags & MMC_RSP_136) { @@ -512,11 +512,11 @@ static void au1xmmc_cmd_complete(struct au1xmmc_host *host, u32 status) /* Figure out errors */ if (status & (SD_STATUS_SC | SD_STATUS_WC | SD_STATUS_RC)) - cmd->error = MMC_ERR_BADCRC; + cmd->error = -EILSEQ; trans = host->flags & (HOST_F_XMIT | HOST_F_RECV); - if (!trans || cmd->error != MMC_ERR_NONE) { + if (!trans || cmd->error) { IRQ_OFF(host, SD_CONFIG_TH | SD_CONFIG_RA|SD_CONFIG_RF); tasklet_schedule(&host->finish_task); @@ -589,7 +589,7 @@ au1xmmc_prepare_data(struct au1xmmc_host *host, struct mmc_data *data) data->sg_len, host->dma.dir); if (host->dma.len == 0) - return MMC_ERR_TIMEOUT; + return -ETIMEDOUT; au_writel(data->blksz - 1, HOST_BLKSIZE(host)); @@ -640,11 +640,11 @@ au1xmmc_prepare_data(struct au1xmmc_host *host, struct mmc_data *data) //IRQ_ON(host, SD_CONFIG_RA|SD_CONFIG_RF); } - return MMC_ERR_NONE; + return 0; dataerr: dma_unmap_sg(mmc_dev(host->mmc),data->sg,data->sg_len,host->dma.dir); - return MMC_ERR_TIMEOUT; + return -ETIMEDOUT; } /* static void au1xmmc_request @@ -656,7 +656,7 @@ static void au1xmmc_request(struct mmc_host* mmc, struct mmc_request* mrq) struct au1xmmc_host *host = mmc_priv(mmc); unsigned int flags = 0; - int ret = MMC_ERR_NONE; + int ret = 0; WARN_ON(irqs_disabled()); WARN_ON(host->status != HOST_S_IDLE); @@ -672,10 +672,10 @@ static void au1xmmc_request(struct mmc_host* mmc, struct mmc_request* mrq) ret = au1xmmc_prepare_data(host, mrq->data); } - if (ret == MMC_ERR_NONE) + if (!ret) ret = au1xmmc_send_command(host, 0, mrq->cmd, flags); - if (ret != MMC_ERR_NONE) { + if (ret) { mrq->cmd->error = ret; au1xmmc_finish_request(host); } @@ -764,10 +764,10 @@ static irqreturn_t au1xmmc_irq(int irq, void *dev_id) if (host->mrq && (status & STATUS_TIMEOUT)) { if (status & SD_STATUS_RAT) - host->mrq->cmd->error = MMC_ERR_TIMEOUT; + host->mrq->cmd->error = -ETIMEDOUT; else if (status & SD_STATUS_DT) - host->mrq->data->error = MMC_ERR_TIMEOUT; + host->mrq->data->error = -ETIMEDOUT; /* In PIO mode, interrupts might still be enabled */ IRQ_OFF(host, SD_CONFIG_NE | SD_CONFIG_TH); diff --git a/drivers/mmc/host/imxmmc.c b/drivers/mmc/host/imxmmc.c index 54bfc9f25596..04458c342812 100644 --- a/drivers/mmc/host/imxmmc.c +++ b/drivers/mmc/host/imxmmc.c @@ -428,11 +428,11 @@ static int imxmci_finish_data(struct imxmci_host *host, unsigned int stat) if ( stat & STATUS_ERR_MASK ) { dev_dbg(mmc_dev(host->mmc), "request failed. status: 0x%08x\n",stat); if(stat & (STATUS_CRC_READ_ERR | STATUS_CRC_WRITE_ERR)) - data->error = MMC_ERR_BADCRC; + data->error = -EILSEQ; else if(stat & STATUS_TIME_OUT_READ) - data->error = MMC_ERR_TIMEOUT; + data->error = -ETIMEDOUT; else - data->error = MMC_ERR_FAILED; + data->error = -EIO; } else { data->bytes_xfered = host->dma_size; } @@ -458,10 +458,10 @@ static int imxmci_cmd_done(struct imxmci_host *host, unsigned int stat) if (stat & STATUS_TIME_OUT_RESP) { dev_dbg(mmc_dev(host->mmc), "CMD TIMEOUT\n"); - cmd->error = MMC_ERR_TIMEOUT; + cmd->error = -ETIMEDOUT; } else if (stat & STATUS_RESP_CRC_ERR && cmd->flags & MMC_RSP_CRC) { dev_dbg(mmc_dev(host->mmc), "cmd crc error\n"); - cmd->error = MMC_ERR_BADCRC; + cmd->error = -EILSEQ; } if(cmd->flags & MMC_RSP_PRESENT) { @@ -482,7 +482,7 @@ static int imxmci_cmd_done(struct imxmci_host *host, unsigned int stat) dev_dbg(mmc_dev(host->mmc), "RESP 0x%08x, 0x%08x, 0x%08x, 0x%08x, error %d\n", cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3], cmd->error); - if (data && (cmd->error == MMC_ERR_NONE) && !(stat & STATUS_ERR_MASK)) { + if (data && !cmd->error && !(stat & STATUS_ERR_MASK)) { if (host->req->data->flags & MMC_DATA_WRITE) { /* Wait for FIFO to be empty before starting DMA write */ @@ -491,7 +491,7 @@ static int imxmci_cmd_done(struct imxmci_host *host, unsigned int stat) if(imxmci_busy_wait_for_status(host, &stat, STATUS_APPL_BUFF_FE, 40, "imxmci_cmd_done DMA WR") < 0) { - cmd->error = MMC_ERR_FIFO; + cmd->error = -EIO; imxmci_finish_data(host, stat); if(host->req) imxmci_finish_request(host, host->req); diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index be730c0a0352..d53e9a8bdaa2 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -154,11 +154,11 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data, } if (status & (MCI_DATACRCFAIL|MCI_DATATIMEOUT|MCI_TXUNDERRUN|MCI_RXOVERRUN)) { if (status & MCI_DATACRCFAIL) - data->error = MMC_ERR_BADCRC; + data->error = -EILSEQ; else if (status & MCI_DATATIMEOUT) - data->error = MMC_ERR_TIMEOUT; + data->error = -ETIMEDOUT; else if (status & (MCI_TXUNDERRUN|MCI_RXOVERRUN)) - data->error = MMC_ERR_FIFO; + data->error = -EIO; status |= MCI_DATAEND; /* @@ -193,12 +193,12 @@ mmci_cmd_irq(struct mmci_host *host, struct mmc_command *cmd, cmd->resp[3] = readl(base + MMCIRESPONSE3); if (status & MCI_CMDTIMEOUT) { - cmd->error = MMC_ERR_TIMEOUT; + cmd->error = -ETIMEDOUT; } else if (status & MCI_CMDCRCFAIL && cmd->flags & MMC_RSP_CRC) { - cmd->error = MMC_ERR_BADCRC; + cmd->error = -EILSEQ; } - if (!cmd->data || cmd->error != MMC_ERR_NONE) { + if (!cmd->data || cmd->error) { if (host->data) mmci_stop_data(host); mmci_request_end(host, cmd->mrq); diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 0cf97edc5f58..60a67dfcda6a 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -263,7 +263,7 @@ mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data) enum dma_data_direction dma_data_dir; BUG_ON(host->dma_ch < 0); - if (data->error != MMC_ERR_NONE) + if (data->error) omap_stop_dma(host->dma_ch); /* Release DMA channel lazily */ mod_timer(&host->dma_timer, jiffies + HZ); @@ -368,7 +368,7 @@ mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd) } } - if (host->data == NULL || cmd->error != MMC_ERR_NONE) { + if (host->data == NULL || cmd->error) { host->mrq = NULL; clk_disable(host->fclk); mmc_request_done(host->mmc, cmd->mrq); @@ -475,14 +475,14 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) if (status & OMAP_MMC_STAT_DATA_TOUT) { dev_dbg(mmc_dev(host->mmc), "data timeout\n"); if (host->data) { - host->data->error |= MMC_ERR_TIMEOUT; + host->data->error = -ETIMEDOUT; transfer_error = 1; } } if (status & OMAP_MMC_STAT_DATA_CRC) { if (host->data) { - host->data->error |= MMC_ERR_BADCRC; + host->data->error = -EILSEQ; dev_dbg(mmc_dev(host->mmc), "data CRC error, bytes left %d\n", host->total_bytes_left); @@ -504,7 +504,7 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) dev_err(mmc_dev(host->mmc), "command timeout, CMD %d\n", host->cmd->opcode); - host->cmd->error = MMC_ERR_TIMEOUT; + host->cmd->error = -ETIMEDOUT; end_command = 1; } } @@ -514,7 +514,7 @@ static irqreturn_t mmc_omap_irq(int irq, void *dev_id) dev_err(mmc_dev(host->mmc), "command CRC error (CMD%d, arg 0x%08x)\n", host->cmd->opcode, host->cmd->arg); - host->cmd->error = MMC_ERR_BADCRC; + host->cmd->error = -EILSEQ; end_command = 1; } else dev_err(mmc_dev(host->mmc), diff --git a/drivers/mmc/host/pxamci.c b/drivers/mmc/host/pxamci.c index ff960334b337..b89e32d1e9b5 100644 --- a/drivers/mmc/host/pxamci.c +++ b/drivers/mmc/host/pxamci.c @@ -226,7 +226,7 @@ static int pxamci_cmd_done(struct pxamci_host *host, unsigned int stat) } if (stat & STAT_TIME_OUT_RESPONSE) { - cmd->error = MMC_ERR_TIMEOUT; + cmd->error = -ETIMEDOUT; } else if (stat & STAT_RES_CRC_ERR && cmd->flags & MMC_RSP_CRC) { #ifdef CONFIG_PXA27x /* @@ -239,11 +239,11 @@ static int pxamci_cmd_done(struct pxamci_host *host, unsigned int stat) pr_debug("ignoring CRC from command %d - *risky*\n", cmd->opcode); } else #endif - cmd->error = MMC_ERR_BADCRC; + cmd->error = -EILSEQ; } pxamci_disable_irq(host, END_CMD_RES); - if (host->data && cmd->error == MMC_ERR_NONE) { + if (host->data && !cmd->error) { pxamci_enable_irq(host, DATA_TRAN_DONE); } else { pxamci_finish_request(host, host->mrq); @@ -264,9 +264,9 @@ static int pxamci_data_done(struct pxamci_host *host, unsigned int stat) host->dma_dir); if (stat & STAT_READ_TIME_OUT) - data->error = MMC_ERR_TIMEOUT; + data->error = -ETIMEDOUT; else if (stat & (STAT_CRC_READ_ERROR|STAT_CRC_WRITE_ERROR)) - data->error = MMC_ERR_BADCRC; + data->error = -EILSEQ; /* * There appears to be a hardware design bug here. There seems to @@ -274,7 +274,7 @@ static int pxamci_data_done(struct pxamci_host *host, unsigned int stat) * This means that if there was an error on any block, we mark all * data blocks as being in error. */ - if (data->error == MMC_ERR_NONE) + if (!data->error) data->bytes_xfered = data->blocks * data->blksz; else data->bytes_xfered = 0; diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index f2bc87ac24f7..3ff33ddfebbb 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -477,17 +477,17 @@ static void sdhci_finish_data(struct sdhci_host *host) /* * Controller doesn't count down when in single block mode. */ - if ((data->blocks == 1) && (data->error == MMC_ERR_NONE)) + if ((data->blocks == 1) && !data->error) blocks = 0; else blocks = readw(host->ioaddr + SDHCI_BLOCK_COUNT); data->bytes_xfered = data->blksz * (data->blocks - blocks); - if ((data->error == MMC_ERR_NONE) && blocks) { + if (!data->error && blocks) { printk(KERN_ERR "%s: Controller signalled completion even " "though there were blocks left.\n", mmc_hostname(host->mmc)); - data->error = MMC_ERR_FAILED; + data->error = -EIO; } if (data->stop) { @@ -495,7 +495,7 @@ static void sdhci_finish_data(struct sdhci_host *host) * The controller needs a reset of internal state machines * upon error conditions. */ - if (data->error != MMC_ERR_NONE) { + if (data->error) { sdhci_reset(host, SDHCI_RESET_CMD); sdhci_reset(host, SDHCI_RESET_DATA); } @@ -530,7 +530,7 @@ static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) printk(KERN_ERR "%s: Controller never released " "inhibit bit(s).\n", mmc_hostname(host->mmc)); sdhci_dumpregs(host); - cmd->error = MMC_ERR_FAILED; + cmd->error = -EIO; tasklet_schedule(&host->finish_tasklet); return; } @@ -551,7 +551,7 @@ static void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) { printk(KERN_ERR "%s: Unsupported response type!\n", mmc_hostname(host->mmc)); - cmd->error = MMC_ERR_INVALID; + cmd->error = -EINVAL; tasklet_schedule(&host->finish_tasklet); return; } @@ -598,7 +598,7 @@ static void sdhci_finish_command(struct sdhci_host *host) } } - host->cmd->error = MMC_ERR_NONE; + host->cmd->error = 0; if (host->cmd->data) host->data = host->cmd->data; @@ -718,7 +718,7 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) host->mrq = mrq; if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)) { - host->mrq->cmd->error = MMC_ERR_TIMEOUT; + host->mrq->cmd->error = -ENOMEDIUM; tasklet_schedule(&host->finish_tasklet); } else sdhci_send_command(host, mrq->cmd); @@ -796,10 +796,35 @@ static int sdhci_get_ro(struct mmc_host *mmc) return !(present & SDHCI_WRITE_PROTECT); } +static void sdhci_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + struct sdhci_host *host; + unsigned long flags; + u32 ier; + + host = mmc_priv(mmc); + + spin_lock_irqsave(&host->lock, flags); + + ier = readl(host->ioaddr + SDHCI_INT_ENABLE); + + ier &= ~SDHCI_INT_CARD_INT; + if (enable) + ier |= SDHCI_INT_CARD_INT; + + writel(ier, host->ioaddr + SDHCI_INT_ENABLE); + writel(ier, host->ioaddr + SDHCI_SIGNAL_ENABLE); + + mmiowb(); + + spin_unlock_irqrestore(&host->lock, flags); +} + static const struct mmc_host_ops sdhci_ops = { .request = sdhci_request, .set_ios = sdhci_set_ios, .get_ro = sdhci_get_ro, + .enable_sdio_irq = sdhci_enable_sdio_irq, }; /*****************************************************************************\ @@ -827,7 +852,7 @@ static void sdhci_tasklet_card(unsigned long param) sdhci_reset(host, SDHCI_RESET_CMD); sdhci_reset(host, SDHCI_RESET_DATA); - host->mrq->cmd->error = MMC_ERR_FAILED; + host->mrq->cmd->error = -ENOMEDIUM; tasklet_schedule(&host->finish_tasklet); } } @@ -855,9 +880,9 @@ static void sdhci_tasklet_finish(unsigned long param) * The controller needs a reset of internal state machines * upon error conditions. */ - if ((mrq->cmd->error != MMC_ERR_NONE) || - (mrq->data && ((mrq->data->error != MMC_ERR_NONE) || - (mrq->data->stop && (mrq->data->stop->error != MMC_ERR_NONE))))) { + if (mrq->cmd->error || + (mrq->data && (mrq->data->error || + (mrq->data->stop && mrq->data->stop->error)))) { /* Some controllers need this kick or reset won't work here */ if (host->chip->quirks & SDHCI_QUIRK_CLOCK_BEFORE_RESET) { @@ -902,13 +927,13 @@ static void sdhci_timeout_timer(unsigned long data) sdhci_dumpregs(host); if (host->data) { - host->data->error = MMC_ERR_TIMEOUT; + host->data->error = -ETIMEDOUT; sdhci_finish_data(host); } else { if (host->cmd) - host->cmd->error = MMC_ERR_TIMEOUT; + host->cmd->error = -ETIMEDOUT; else - host->mrq->cmd->error = MMC_ERR_TIMEOUT; + host->mrq->cmd->error = -ETIMEDOUT; tasklet_schedule(&host->finish_tasklet); } @@ -937,13 +962,12 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask) } if (intmask & SDHCI_INT_TIMEOUT) - host->cmd->error = MMC_ERR_TIMEOUT; - else if (intmask & SDHCI_INT_CRC) - host->cmd->error = MMC_ERR_BADCRC; - else if (intmask & (SDHCI_INT_END_BIT | SDHCI_INT_INDEX)) - host->cmd->error = MMC_ERR_FAILED; + host->cmd->error = -ETIMEDOUT; + else if (intmask & (SDHCI_INT_CRC | SDHCI_INT_END_BIT | + SDHCI_INT_INDEX)) + host->cmd->error = -EILSEQ; - if (host->cmd->error != MMC_ERR_NONE) + if (host->cmd->error) tasklet_schedule(&host->finish_tasklet); else if (intmask & SDHCI_INT_RESPONSE) sdhci_finish_command(host); @@ -970,13 +994,11 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) } if (intmask & SDHCI_INT_DATA_TIMEOUT) - host->data->error = MMC_ERR_TIMEOUT; - else if (intmask & SDHCI_INT_DATA_CRC) - host->data->error = MMC_ERR_BADCRC; - else if (intmask & SDHCI_INT_DATA_END_BIT) - host->data->error = MMC_ERR_FAILED; + host->data->error = -ETIMEDOUT; + else if (intmask & (SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_END_BIT)) + host->data->error = -EILSEQ; - if (host->data->error != MMC_ERR_NONE) + if (host->data->error) sdhci_finish_data(host); else { if (intmask & (SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL)) @@ -1001,6 +1023,7 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) irqreturn_t result; struct sdhci_host* host = dev_id; u32 intmask; + int cardint = 0; spin_lock(&host->lock); @@ -1045,6 +1068,11 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) intmask &= ~SDHCI_INT_BUS_POWER; + if (intmask & SDHCI_INT_CARD_INT) + cardint = 1; + + intmask &= ~SDHCI_INT_CARD_INT; + if (intmask) { printk(KERN_ERR "%s: Unexpected interrupt 0x%08x.\n", mmc_hostname(host->mmc), intmask); @@ -1059,6 +1087,12 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) out: spin_unlock(&host->lock); + /* + * We have to delay this as it calls back into the driver. + */ + if (cardint) + mmc_signal_sdio_irq(host->mmc); + return result; } @@ -1298,7 +1332,8 @@ static int __devinit sdhci_probe_slot(struct pci_dev *pdev, int slot) mmc->ops = &sdhci_ops; mmc->f_min = host->max_clk / 256; mmc->f_max = host->max_clk; - mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_MULTIWRITE | MMC_CAP_BYTEBLOCK; + mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_MULTIWRITE | + MMC_CAP_BYTEBLOCK | MMC_CAP_SDIO_IRQ; if (caps & SDHCI_CAN_DO_HISPD) mmc->caps |= MMC_CAP_SD_HIGHSPEED; diff --git a/drivers/mmc/host/tifm_sd.c b/drivers/mmc/host/tifm_sd.c index 8b736e968447..b4a56e5e5132 100644 --- a/drivers/mmc/host/tifm_sd.c +++ b/drivers/mmc/host/tifm_sd.c @@ -404,14 +404,14 @@ static void tifm_sd_check_status(struct tifm_sd *host) struct tifm_dev *sock = host->dev; struct mmc_command *cmd = host->req->cmd; - if (cmd->error != MMC_ERR_NONE) + if (cmd->error) goto finish_request; if (!(host->cmd_flags & CMD_READY)) return; if (cmd->data) { - if (cmd->data->error != MMC_ERR_NONE) { + if (cmd->data->error) { if ((host->cmd_flags & SCMD_ACTIVE) && !(host->cmd_flags & SCMD_READY)) return; @@ -504,7 +504,7 @@ static void tifm_sd_card_event(struct tifm_dev *sock) { struct tifm_sd *host; unsigned int host_status = 0; - int cmd_error = MMC_ERR_NONE; + int cmd_error = 0; struct mmc_command *cmd = NULL; unsigned long flags; @@ -521,15 +521,15 @@ static void tifm_sd_card_event(struct tifm_dev *sock) writel(host_status & TIFM_MMCSD_ERRMASK, sock->addr + SOCK_MMCSD_STATUS); if (host_status & TIFM_MMCSD_CTO) - cmd_error = MMC_ERR_TIMEOUT; + cmd_error = -ETIMEDOUT; else if (host_status & TIFM_MMCSD_CCRC) - cmd_error = MMC_ERR_BADCRC; + cmd_error = -EILSEQ; if (cmd->data) { if (host_status & TIFM_MMCSD_DTO) - cmd->data->error = MMC_ERR_TIMEOUT; + cmd->data->error = -ETIMEDOUT; else if (host_status & TIFM_MMCSD_DCRC) - cmd->data->error = MMC_ERR_BADCRC; + cmd->data->error = -EILSEQ; } writel(TIFM_FIFO_INT_SETALL, @@ -722,7 +722,7 @@ static void tifm_sd_request(struct mmc_host *mmc, struct mmc_request *mrq) return; err_out: - mrq->cmd->error = MMC_ERR_TIMEOUT; + mrq->cmd->error = -ETIMEDOUT; mmc_request_done(mmc, mrq); } @@ -1012,9 +1012,9 @@ static void tifm_sd_remove(struct tifm_dev *sock) writel(TIFM_FIFO_INT_SETALL, sock->addr + SOCK_DMA_FIFO_INT_ENABLE_CLEAR); writel(0, sock->addr + SOCK_DMA_FIFO_INT_ENABLE_SET); - host->req->cmd->error = MMC_ERR_TIMEOUT; + host->req->cmd->error = -ENOMEDIUM; if (host->req->stop) - host->req->stop->error = MMC_ERR_TIMEOUT; + host->req->stop->error = -ENOMEDIUM; tasklet_schedule(&host->finish_tasklet); } spin_unlock_irqrestore(&sock->lock, flags); diff --git a/drivers/mmc/host/wbsd.c b/drivers/mmc/host/wbsd.c index e0c9808fd424..34a5496f13d5 100644 --- a/drivers/mmc/host/wbsd.c +++ b/drivers/mmc/host/wbsd.c @@ -317,7 +317,7 @@ static inline void wbsd_get_short_reply(struct wbsd_host *host, * Correct response type? */ if (wbsd_read_index(host, WBSD_IDX_RSPLEN) != WBSD_RSP_SHORT) { - cmd->error = MMC_ERR_INVALID; + cmd->error = -EILSEQ; return; } @@ -337,7 +337,7 @@ static inline void wbsd_get_long_reply(struct wbsd_host *host, * Correct response type? */ if (wbsd_read_index(host, WBSD_IDX_RSPLEN) != WBSD_RSP_LONG) { - cmd->error = MMC_ERR_INVALID; + cmd->error = -EILSEQ; return; } @@ -372,7 +372,7 @@ static void wbsd_send_command(struct wbsd_host *host, struct mmc_command *cmd) for (i = 3; i >= 0; i--) outb((cmd->arg >> (i * 8)) & 0xff, host->base + WBSD_CMDR); - cmd->error = MMC_ERR_NONE; + cmd->error = 0; /* * Wait for the request to complete. @@ -392,13 +392,13 @@ static void wbsd_send_command(struct wbsd_host *host, struct mmc_command *cmd) /* Card removed? */ if (isr & WBSD_INT_CARD) - cmd->error = MMC_ERR_TIMEOUT; + cmd->error = -ENOMEDIUM; /* Timeout? */ else if (isr & WBSD_INT_TIMEOUT) - cmd->error = MMC_ERR_TIMEOUT; + cmd->error = -ETIMEDOUT; /* CRC? */ else if ((cmd->flags & MMC_RSP_CRC) && (isr & WBSD_INT_CRC)) - cmd->error = MMC_ERR_BADCRC; + cmd->error = -EILSEQ; /* All ok */ else { if (cmd->flags & MMC_RSP_136) @@ -585,7 +585,7 @@ static void wbsd_prepare_data(struct wbsd_host *host, struct mmc_data *data) ((blksize >> 4) & 0xF0) | WBSD_DATA_WIDTH); wbsd_write_index(host, WBSD_IDX_PBSLSB, blksize & 0xFF); } else { - data->error = MMC_ERR_INVALID; + data->error = -EINVAL; return; } @@ -607,7 +607,7 @@ static void wbsd_prepare_data(struct wbsd_host *host, struct mmc_data *data) */ BUG_ON(size > 0x10000); if (size > 0x10000) { - data->error = MMC_ERR_INVALID; + data->error = -EINVAL; return; } @@ -669,7 +669,7 @@ static void wbsd_prepare_data(struct wbsd_host *host, struct mmc_data *data) } } - data->error = MMC_ERR_NONE; + data->error = 0; } static void wbsd_finish_data(struct wbsd_host *host, struct mmc_data *data) @@ -724,8 +724,8 @@ static void wbsd_finish_data(struct wbsd_host *host, struct mmc_data *data) "%d bytes left.\n", mmc_hostname(host->mmc), count); - if (data->error == MMC_ERR_NONE) - data->error = MMC_ERR_FAILED; + if (!data->error) + data->error = -EIO; } else { /* * Transfer data from DMA buffer to @@ -735,7 +735,7 @@ static void wbsd_finish_data(struct wbsd_host *host, struct mmc_data *data) wbsd_dma_to_sg(host, data); } - if (data->error != MMC_ERR_NONE) { + if (data->error) { if (data->bytes_xfered) data->bytes_xfered -= data->blksz; } @@ -767,11 +767,10 @@ static void wbsd_request(struct mmc_host *mmc, struct mmc_request *mrq) host->mrq = mrq; /* - * If there is no card in the slot then - * timeout immediatly. + * Check that there is actually a card in the slot. */ if (!(host->flags & WBSD_FCARD_PRESENT)) { - cmd->error = MMC_ERR_TIMEOUT; + cmd->error = -ENOMEDIUM; goto done; } @@ -807,7 +806,7 @@ static void wbsd_request(struct mmc_host *mmc, struct mmc_request *mrq) "supported by this controller.\n", mmc_hostname(host->mmc), cmd->opcode); #endif - cmd->error = MMC_ERR_INVALID; + cmd->error = -EINVAL; goto done; }; @@ -819,7 +818,7 @@ static void wbsd_request(struct mmc_host *mmc, struct mmc_request *mrq) if (cmd->data) { wbsd_prepare_data(host, cmd->data); - if (cmd->data->error != MMC_ERR_NONE) + if (cmd->data->error) goto done; } @@ -830,7 +829,7 @@ static void wbsd_request(struct mmc_host *mmc, struct mmc_request *mrq) * will be finished after the data has * transfered. */ - if (cmd->data && (cmd->error == MMC_ERR_NONE)) { + if (cmd->data && !cmd->error) { /* * Dirty fix for hardware bug. */ @@ -1033,7 +1032,7 @@ static void wbsd_tasklet_card(unsigned long param) mmc_hostname(host->mmc)); wbsd_reset(host); - host->mrq->cmd->error = MMC_ERR_FAILED; + host->mrq->cmd->error = -ENOMEDIUM; tasklet_schedule(&host->finish_tasklet); } @@ -1097,7 +1096,7 @@ static void wbsd_tasklet_crc(unsigned long param) DBGF("CRC error\n"); - data->error = MMC_ERR_BADCRC; + data->error = -EILSEQ; tasklet_schedule(&host->finish_tasklet); @@ -1121,7 +1120,7 @@ static void wbsd_tasklet_timeout(unsigned long param) DBGF("Timeout\n"); - data->error = MMC_ERR_TIMEOUT; + data->error = -ETIMEDOUT; tasklet_schedule(&host->finish_tasklet); |