GIT aa3364327d40d4c8bcfb3f11a5eecb5d55f325ae git+ssh://master.kernel.org/pub/scm/linux/kernel/git/drzeus/mmc.git#for-andrew commit Author: Pierre Ossman Date: Tue Jul 31 11:37:59 2007 +0200 mmc: MMFIX: build fix (spotted by Andrew) Signed-off-by: Pierre Ossman commit 38adc9dedec42d0dcdd1dd64d9754703b9cee04e Author: Adrian Bunk Date: Sun Jul 29 16:58:09 2007 +0200 make struct sdio_dev_attrs[] static On Wed, Jul 25, 2007 at 04:03:04AM -0700, Andrew Morton wrote: >... > Changes since 2.6.22-rc6-mm1: >... > git-mmc.patch >... > git trees >... sdio_dev_attrs[] can become static. Signed-off-by: Adrian Bunk Signed-off-by: Pierre Ossman commit c9b402107af148bfe151b0462577c5c4d16b80aa Author: Anderson Briglia Date: Sat Jan 6 22:57:35 2007 +0100 mmc: Lock sysfs interface Implement MMC password force erase, remove password, change password, unlock card and assign password operations. It uses the sysfs mechanism to send commands to the MMC subsystem. Signed-off-by: Carlos Eduardo Aguiar Signed-off-by: Anderson Lizardo Signed-off-by: Anderson Briglia Signed-off-by: Pierre Ossman commit dd3668ff9d79114cd1590cfdfc3e46fdd3f297f6 Author: Anderson Briglia Date: Fri May 18 13:32:40 2007 +0200 mmc: Implement card lock/unlock operation, using the MMC_LOCK_UNLOCK command. Signed-off-by: Carlos Eduardo Aguiar Signed-off-by: Anderson Lizardo Signed-off-by: Anderson Briglia Signed-off-by: Pierre Ossman commit f787ea7d0837c9357a2b81019e54f144590dc860 Author: Anderson Briglia Date: Fri May 18 13:22:13 2007 +0200 mmc: Implement key retention operations. Signed-off-by: Carlos Eduardo Aguiar Signed-off-by: Anderson Lizardo Signed-off-by: Anderson Briglia Signed-off-by: Pierre Ossman commit 1fa8226f9524f9bb9c7f85b1c3f79a3c3e1f9d9c Author: Anderson Briglia Date: Fri May 18 13:15:46 2007 +0200 mmc: Lockable card state When a card is locked, only commands from the "basic" and "lock card" classes are accepted. To be able to use the other commands, the card must be unlocked first. This patch prevents the device drivers from probing the locked cards. Device probing must be triggered sometime later to make the card available to the block driver. Signed-off-by: Carlos Eduardo Aguiar Signed-off-by: Anderson Lizardo Signed-off-by: Anderson Briglia Signed-off-by: David Brownell Signed-off-by: Pierre Ossman commit c945bc408e3e95d8d56267715b45830bf7d672d2 Author: Pierre Ossman Date: Sat Jul 28 01:09:47 2007 +0200 sdio: MMFIX: add sdio type to bus insertion dmesg output Signed-off-by: Pierre Ossman commit 19c273cd906f3375494133ebf745605eac6c56cb Author: Nicolas Pitre Date: Tue Jul 24 02:09:39 2007 -0400 sdio: add interface for host side SDIO interrupt reporting Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman commit a8787d89f163a4739d496e3d9a22397442bebf89 Author: Pierre Ossman Date: Thu Jul 26 00:18:50 2007 +0200 MMFIX: strip some whitespace in sdio_uart driver Signed-off-by: Pierre Ossman commit 4b626b5f229f5226bbf195de7baa83bee508b96d Author: Nicolas Pitre Date: Thu Jul 5 13:46:13 2007 -0400 sdio: reduce verbosity level of the SDIO_CCCR_INTx reading error message They happen most of the time when the card has just been removed, hence it isn't a critical event. Avoid cluttering the console with them. Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman commit 0ea94f9c79287822decbd29fd0871b3d72178619 Author: Pierre Ossman Date: Fri Jul 6 13:35:01 2007 +0200 sdio: support IO_RW_EXTENDED Support the multi-byte transfer operation, including handlers for common operations like writel()/readl(). Signed-off-by: Pierre Ossman commit 035b57060ebd353ff0eb322446a7028854199798 Author: Nicolas Pitre Date: Wed Jul 4 23:40:34 2007 -0400 sdio: add /proc interface to sdio_uart driver This mimics what the serial_core does. Useful for diagnostics. Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman commit 40c2c017a98b412cec87568df73d2ddc18363474 Author: Nicolas Pitre Date: Wed Jul 4 23:40:33 2007 -0400 sdio: multiple fixes to the sdio_uart driver This is mainly to make the driver robust against nasty situations like removal of the card when a tty is still opened, reinsertion of a card while that same tty hasn't been closed yet, etc. etc. To do so the locking and resource handling has been tightened up, locks have been renamed to make things clearer, the claiming of the SDIO host made safe against a SDIO function structure suddenly disappearing from under our feet, and proper error conditions are now reported to the tty layer when the device has gone away. Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman commit b6380453fefea004012cc38e653fd5454d0ccbf8 Author: Nicolas Pitre Date: Sat Jun 30 02:04:21 2007 -0400 sdio: UART/GPS driver This currently only accepts the GPS class since that's all I have for testing. Tested with a Matsushita GPS and gpsd version 2.34. Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman commit 17c5079009f9c7c9f8e03a6638f1bc519c63d55d Author: Nicolas Pitre Date: Sat Jun 30 16:29:41 2007 +0200 sdio: core support for SDIO function interrupt Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman commit c93d2e6e659964c6df98808e1b81e81c8d73dad8 Author: Nicolas Pitre Date: Sat Jun 30 16:21:52 2007 +0200 sdio: allow for mmc_claim_host to be aborted It is sometimes necessary to give up on trying to claim the host lock, especially if that happens in a thread that has to be stopped. While at it, fix the description for mmc_claim_host() which was wrong. Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman commit 6e667d485035a19db474582c97f7969dc7814b46 Author: Pierre Ossman Date: Sun Jun 17 12:48:00 2007 +0200 sdio: set name directly in sdio driver Signed-off-by: Pierre Ossman commit 9f5af819da7bfeb8e028220a442402a29772626e Author: Nicolas Pitre Date: Sat Jun 16 21:40:07 2007 -0400 sdio: defines for some standard interface types Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman commit f3852060bc333f640bd1f72919a1f06a134b0fd3 Author: Pierre Ossman Date: Sun Jun 17 11:47:41 2007 +0200 sdio: export modalias to sysfs Signed-off-by: Pierre Ossman commit a3105b2a14c86eef62b7bf840fe4920b42ca0be7 Author: Pierre Ossman Date: Sun Jun 17 11:42:21 2007 +0200 sdio: add basic sysfs attributes Signed-off-by: Pierre Ossman commit 7d1bee694994184eaf98860ac5b7b695849661b0 Author: Pierre Ossman Date: Sun Jun 17 11:34:23 2007 +0200 sdio: add modalias support Signed-off-by: Pierre Ossman commit 6d59ede8e2b3095e84e746aa112ba4d571b1f322 Author: Pierre Ossman Date: Sun Jun 17 11:18:46 2007 +0200 mmc: whip bus uevent handler into shape Make the mmc bus uevent callback look like all other subsystems. Signed-off-by: Pierre Ossman commit 42666411c3840fece94c29d50725c601bfa453f8 Author: Pierre Ossman Date: Sat Jun 16 16:04:21 2007 +0200 sdio: include matching device id to probe function commit 1efc74f6e495fcf5a0919deb70585aa0981db7a2 Author: Pierre Ossman Date: Sat Jun 16 15:54:55 2007 +0200 sdio: add device id table and matching Signed-off-by: Pierre Ossman commit f1c020b2bbb8dd3f6b82ace91f22b73feb16157f Author: Nicolas Pitre Date: Sat Jun 16 02:07:53 2007 -0400 mmc: initialize mmc subsystem with subsys_initcall() The problem is that the sdio_bus must be registered before any SDIO drivers are registered against it otherwise the kernel sulks. Because the sdio_bus registration happens through module_init (equivalent to device_initcall), then any SDIO drivers linked before the SDIO core code in the kernel will be initialized first. Upcoming SDIO function drivers are likely to be located outside the drivers/mmc directory as it is common practice to group drivers according to their function rather than the bus they use. SDIO drivers are therefore likely to appear at random location in the kernel link. To make sure the sdio_bus is always initialized before any SDIO drivers, let's move the MMC init to the subsys_initcall level. Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman commit eeb91cb984293614c01fa9e700a60f74be66f575 Author: Nicolas Pitre Date: Sat Jun 16 02:07:11 2007 -0400 sdio: fix timeout handling in sdio_enable_func() First it is bad to do direct comparisons on jiffies as this doesn't cope with jiffy wraparounds. Let's use the proper comparator instead. Then it would be nice to let the caller know if a failure is due to the timeout or something else. Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman commit 18e21a917229a660bca5335a2ea9442c1dac530f Author: Nicolas Pitre Date: Sat Jun 16 02:06:47 2007 -0400 sdio: link unknown CIS tuples to the sdio_func structure This way those tuples that the core cares about are consumed by the core code, and tuples that only function drivers might make sense of are available to drivers. Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman commit 47033f79eef082801aa52563789c715fd24bd0bc Author: Nicolas Pitre Date: Sat Jun 16 02:06:11 2007 -0400 sdio: more CIS parsing for CISTPL_FUNCE Most values are pretty printed at this point. They only need to be remembered in some struct sdio_func fields eventually. Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman commit 79b8f677eb73476c7afd9d132aa03c6d68383b11 Author: Nicolas Pitre Date: Sat Jun 16 02:05:48 2007 -0400 sdio: parse CIS information for each function Because the common CIS information applies to all functions, it is read before each function's own CIS. The later may override the common one. Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman commit 910369d83622c20ea6b2f6342b4a7e4bab5b8927 Author: Nicolas Pitre Date: Sat Jun 16 02:04:16 2007 -0400 sdio: initial CIS parsing code Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman commit d6d46f4611a4541dc797867b2541fc54a2aa071d Author: Pierre Ossman Date: Mon Jun 11 21:01:00 2007 +0200 sdio: basic parsing of FBR Signed-off-by: Pierre Ossman commit 728738819c241ba6bb7ed93257857cfb51d46882 Author: Pierre Ossman Date: Mon Jun 11 20:25:43 2007 +0200 sdio: read and decode interesting parts of the CCCR Signed-off-by: Pierre Ossman commit 3586ee37d8859a4e79475f0189a23678d7c1ce9d Author: Nicolas Pitre Date: Mon Jun 11 19:47:38 2007 +0200 sdio: add register defines for FBR Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman commit 27d60e4b24c946664b0b90db3190dd07c3815851 Author: Nicolas Pitre Date: Mon Jun 11 19:39:53 2007 +0200 sdio: add remaining CCCR defines Signed-off-by: Nicolas Pitre Signed-off-by: Pierre Ossman commit 1bda3fcb137218dd9e464f9b61204e8711c815be Author: Pierre Ossman Date: Sun May 27 14:22:37 2007 +0200 mmc: enable/disable functions for SDIO Like many other buses, the devices (functions) on the SDIO bus must be enabled before they can be used. Add functions that allow drivers to do so. Signed-off-by: Pierre Ossman commit 8ed7cad71ed311a39feaddb673ad2d9ba6adf0f1 Author: Pierre Ossman Date: Sun May 27 12:57:15 2007 +0200 mmc: add basic SDIO I/O operations Add command wrappers that simplify register access from SDIO function drivers. Signed-off-by: Pierre Ossman commit a5153b6c7fd75031979f861840fd8935073d0a49 Author: Pierre Ossman Date: Sun May 27 12:00:02 2007 +0200 mmc: add SDIO driver handling Add basic driver handling to the SDIO device model. Signed-off-by: Pierre Ossman commit 24c6d0852fc16361a77211546311aaa3a2b53636 Author: Pierre Ossman Date: Sat May 26 13:48:18 2007 +0200 mmc: basic SDIO device model Add the sdio bus type and basic device handling. Signed-off-by: Pierre Ossman commit b39d53b1662cf20c3f2f54473fa2304326552b75 Author: Pierre Ossman Date: Tue May 22 20:25:21 2007 +0200 mmc: implement SDIO IO_RW_DIRECT operation Signed-off-by: Pierre Ossman commit 6e53631dcb3f2db7f8fe4fda9c790dd4191f991d Author: Pierre Ossman Date: Mon May 21 20:23:20 2007 +0200 mmc: detect SDIO cards Really basic init sequence for SDIO cards. Signed-off-by: Pierre Ossman commit 8e69be0c1afbe750afade990446375e49cf906c2 Author: Pierre Ossman Date: Sun Jul 22 23:08:30 2007 +0200 mmc: improve error code feedback Now that we use "normal" error codes, improve the reporting and response to error codes in the core. Signed-off-by: Pierre Ossman commit 51722ecab723c6cd617792621acfb3c55a0a244d Author: Pierre Ossman Date: Sun Jul 22 22:18:46 2007 +0200 mmc: remove custom error codes Convert the MMC layer to use standard error codes and not its own, incompatible values. Signed-off-by: Pierre Ossman commit 401d78ba0cd8f9c772ca7681bf24d7a5fe85ec81 Author: Pierre Ossman Date: Tue Jul 24 21:53:43 2007 +0200 mmc: add missing printk levels Some printk:s were missing an explicit level. Signed-off-by: Pierre Ossman commit 9c28ffd7298fe0cab98e5c4ceae81f818e12f9b4 Author: Pierre Ossman Date: Tue Jul 24 21:11:47 2007 +0200 mmc: remove confusing flag The MMC_DATA_MULTI flag never had a proper definition of what it means, so remove it and let the drivers check the block count in the request. Signed-off-by: Pierre Ossman commit 26c0c10eb23549d34a19f034fcb79d501f63a4b4 Author: Pierre Ossman Date: Tue Jul 24 20:38:53 2007 +0200 mmc: remove BYTEBLOCK capability Remove the BYTEBLOCK capability and let the broken hosts fail the requests with -EINVAL instead. Signed-off-by: Pierre Ossman commit c521f5c9d8a3a39df3078586e1865698d71f90de Author: Pierre Ossman Date: Tue Jul 24 19:16:54 2007 +0200 mmc: mmc_set_data_timeout() parameter write is redundant The write parameter in mmc_set_data_timeout() is redundant as the data structure contains information about the direction of the transfer. Signed-off-by: Pierre Ossman commit 6f47c8fc2c152dfad61380886ba768915a96d5da Author: Pierre Ossman Date: Mon Jul 23 00:34:07 2007 +0200 mmc: read ext_csd version number Make sure we do not try to parse a structure we do not understand. Signed-off-by: Pierre Ossman commit a929822462b507a9f5dd018f1fc2c9dd0fab5a0c Author: Pierre Ossman Date: Sun Jul 22 23:08:30 2007 +0200 mmc: improve error code feedback Now that we use "normal" error codes, improve the reporting and response to error codes in the core. Signed-off-by: Pierre Ossman commit bac91074e6b0e7452c6f73eb5e4d2584497cd638 Author: Pierre Ossman Date: Sun Jul 22 22:18:46 2007 +0200 mmc: remove custom error codes Convert the MMC layer to use standard error codes and not its own, incompatible values. Signed-off-by: Pierre Ossman drivers/mmc/card/Kconfig | 7 drivers/mmc/card/Makefile | 2 drivers/mmc/card/block.c | 9 drivers/mmc/card/sdio_uart.c | 1139 +++++++++++++++++++++++++++++++++++++++ drivers/mmc/core/Kconfig | 13 drivers/mmc/core/Makefile | 5 drivers/mmc/core/bus.c | 49 +- drivers/mmc/core/core.c | 109 +++- drivers/mmc/core/lock.c | 199 +++++++ drivers/mmc/core/lock.h | 51 ++ drivers/mmc/core/mmc.c | 106 ++-- drivers/mmc/core/mmc_ops.c | 155 +++++ drivers/mmc/core/mmc_ops.h | 3 drivers/mmc/core/sd.c | 96 ++- drivers/mmc/core/sd_ops.c | 50 +- drivers/mmc/core/sdio.c | 336 ++++++++++++ drivers/mmc/core/sdio_bus.c | 251 +++++++++ drivers/mmc/core/sdio_bus.h | 22 + drivers/mmc/core/sdio_cis.c | 286 ++++++++++ drivers/mmc/core/sdio_cis.h | 19 + drivers/mmc/core/sdio_io.c | 395 ++++++++++++++ drivers/mmc/core/sdio_irq.c | 235 ++++++++ drivers/mmc/core/sdio_ops.c | 143 +++++ drivers/mmc/core/sdio_ops.h | 22 + drivers/mmc/host/at91_mci.c | 17 - drivers/mmc/host/au1xmmc.c | 58 +- drivers/mmc/host/imxmmc.c | 16 - drivers/mmc/host/mmci.c | 20 - drivers/mmc/host/omap.c | 12 drivers/mmc/host/pxamci.c | 12 drivers/mmc/host/sdhci.c | 53 +- drivers/mmc/host/tifm_sd.c | 31 + drivers/mmc/host/wbsd.c | 43 + include/linux/mmc/card.h | 25 + include/linux/mmc/core.h | 37 + include/linux/mmc/host.h | 18 + include/linux/mmc/mmc.h | 9 include/linux/mmc/sdio.h | 157 +++++ include/linux/mmc/sdio_func.h | 138 +++++ include/linux/mmc/sdio_ids.h | 23 + include/linux/mod_devicetable.h | 11 scripts/mod/file2alias.c | 20 + 42 files changed, 4106 insertions(+), 296 deletions(-) diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig index a49cb97..aa8a4e4 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 cf8c939..fc5a784 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 93fe2e5..ab51068 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 m 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 m 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); @@ -229,8 +229,6 @@ static int mmc_blk_issue_rq(struct mmc_q if (brq.data.blocks > card->host->max_blk_count) brq.data.blocks = card->host->max_blk_count; - mmc_set_data_timeout(&brq.data, card, rq_data_dir(req) != READ); - /* * If the host doesn't support multiple block writes, force * block writes to single block. SD cards are excepted from @@ -243,7 +241,6 @@ static int mmc_blk_issue_rq(struct mmc_q brq.data.blocks = 1; if (brq.data.blocks > 1) { - brq.data.flags |= MMC_DATA_MULTI; brq.mrq.stop = &brq.stop; readcmd = MMC_READ_MULTIPLE_BLOCK; writecmd = MMC_WRITE_MULTIPLE_BLOCK; @@ -261,6 +258,8 @@ static int mmc_blk_issue_rq(struct mmc_q brq.data.flags |= MMC_DATA_WRITE; } + mmc_set_data_timeout(&brq.data, card); + brq.data.sg = mq->sg; brq.data.sg_len = mmc_queue_map_sg(mq); diff --git a/drivers/mmc/card/sdio_uart.c b/drivers/mmc/card/sdio_uart.c new file mode 100644 index 0000000..a2642ed --- /dev/null +++ b/drivers/mmc/card/sdio_uart.c @@ -0,0 +1,1139 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +#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; + 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; + } + sdio_claim_host(port->func); + mutex_unlock(&port->func_lock); + return 0; +} + +static inline void sdio_uart_release_func(struct sdio_uart_port *port) +{ + 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; + + iir = sdio_in(port, UART_IIR); + if (iir & UART_IIR_NO_INT) + return; + 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); +} + +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_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/Kconfig b/drivers/mmc/core/Kconfig index ab37a6d..becaa07 100644 --- a/drivers/mmc/core/Kconfig +++ b/drivers/mmc/core/Kconfig @@ -14,3 +14,16 @@ config MMC_UNSAFE_RESUME This option is usually just for embedded systems which use a MMC/SD card for rootfs. Most people should say N here. +config MMC_PASSWORDS + boolean "MMC card lock/unlock passwords (EXPERIMENTAL)" + depends on EXPERIMENTAL + select KEYS + help + Say Y here to enable the use of passwords to lock and unlock + MMC cards. This uses the access key retention support, using + request_key to look up the key associated with each card. + + For example, if you have an MMC card that was locked using + Symbian OS on your cell phone, you won't be able to read it + on Linux without this support. + diff --git a/drivers/mmc/core/Makefile b/drivers/mmc/core/Makefile index 3fdd08c..d507c56 100644 --- a/drivers/mmc/core/Makefile +++ b/drivers/mmc/core/Makefile @@ -8,5 +8,8 @@ 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 +mmc_core-$(CONFIG_MMC_PASSWORDS) += lock.o diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c index fe0e785..fa9e347 100644 --- a/drivers/mmc/core/bus.c +++ b/drivers/mmc/core/bus.c @@ -34,6 +34,8 @@ static ssize_t mmc_type_show(struct devi 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; } @@ -48,9 +50,21 @@ static struct device_attribute mmc_dev_a * This currently matches any MMC driver to any MMC card - drivers * themselves make the decision whether to drive this card in their * probe method. + * + * We also fail for all locked cards; drivers expect to be able to do block + * I/O still on probe(), which is not possible while the card is locked. + * Device probing must be triggered sometime later to make the card available + * to the block driver. */ static int mmc_bus_match(struct device *dev, struct device_driver *drv) { + struct mmc_card *card = dev_to_mmc_card(dev); + + if (mmc_card_locked(card)) { + dev_dbg(&card->dev, "card is locked; binding is deferred\n"); + return 0; + } + return 1; } @@ -59,28 +73,34 @@ mmc_bus_uevent(struct device *dev, char 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; @@ -223,6 +243,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 bfd2ae5..89bf6de 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -29,12 +29,16 @@ #include #include "core.h" #include "bus.h" #include "host.h" +#include "lock.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; @@ -220,13 +224,11 @@ EXPORT_SYMBOL(mmc_wait_for_cmd); * mmc_set_data_timeout - set the timeout for a data command * @data: data phase for command * @card: the MMC card associated with the data transfer - * @write: flag to differentiate reads from writes * * Computes the data timeout parameters according to the * correct algorithm given the card type. */ -void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card, - int write) +void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card) { unsigned int mult; @@ -239,7 +241,7 @@ void mmc_set_data_timeout(struct mmc_dat * Scale up the multiplier (and therefore the timeout) by * the r2w factor for writes. */ - if (write) + if (data->flags & MMC_DATA_WRITE) mult <<= card->csd.r2w_factor; data->timeout_ns = card->csd.tacc_ns * mult; @@ -255,7 +257,7 @@ void mmc_set_data_timeout(struct mmc_dat timeout_us += data->timeout_clks * 1000 / (card->host->ios.clock / 1000); - if (write) + if (data->flags & MMC_DATA_WRITE) limit_us = 250000; else limit_us = 100000; @@ -272,15 +274,20 @@ void mmc_set_data_timeout(struct mmc_dat 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 +295,24 @@ void mmc_claim_host(struct mmc_host *hos 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 @@ -597,24 +609,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 +751,45 @@ 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 = mmc_register_key_type(); + if (ret) + goto unregister_host_class; + + ret = sdio_register_bus(); + if (ret) + goto unregister_key; + + return 0; + +unregister_key: + mmc_unregister_key_type(); +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) { + mmc_unregister_key_type(); + 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/lock.c b/drivers/mmc/core/lock.c new file mode 100644 index 0000000..05b4d4a --- /dev/null +++ b/drivers/mmc/core/lock.c @@ -0,0 +1,199 @@ +/* + * linux/drivers/mmc/core/lock.h + * + * Copyright 2006 Instituto Nokia de Tecnologia (INdT), All Rights Reserved. + * 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 version 2 as + * published by the Free Software Foundation. + * + * MMC password key handling. + */ + +#include +#include +#include + +#include +#include +#include + +#include "sysfs.h" +#include "mmc_ops.h" +#include "lock.h" + +#define MMC_KEYLEN_MAXBYTES 32 + +#define dev_to_mmc_card(d) container_of(d, struct mmc_card, dev) + +static int mmc_key_instantiate(struct key *key, const void *data, size_t datalen) +{ + struct mmc_key_payload *mpayload; + int ret; + + ret = -EINVAL; + if (datalen <= 0 || datalen > MMC_KEYLEN_MAXBYTES || !data) { + pr_debug("Invalid data\n"); + goto error; + } + + ret = key_payload_reserve(key, datalen); + if (ret < 0) { + pr_debug("ret = %d\n", ret); + goto error; + } + + ret = -ENOMEM; + mpayload = kmalloc(sizeof(*mpayload) + datalen, GFP_KERNEL); + if (!mpayload) { + pr_debug("Unable to allocate mpayload structure\n"); + goto error; + } + mpayload->datalen = datalen; + memcpy(mpayload->data, data, datalen); + + rcu_assign_pointer(key->payload.data, mpayload); + + /* ret = 0 if there is no error */ + ret = 0; + +error: + return ret; +} + +static int mmc_key_match(const struct key *key, const void *description) +{ + return strcmp(key->description, description) == 0; +} + +/* + * dispose of the data dangling from the corpse of a mmc key + */ +static void mmc_key_destroy(struct key *key) +{ + struct mmc_key_payload *mpayload = key->payload.data; + + kfree(mpayload); +} + +static struct key_type mmc_key_type = { + .name = "mmc", + .def_datalen = MMC_KEYLEN_MAXBYTES, + .instantiate = mmc_key_instantiate, + .match = mmc_key_match, + .destroy = mmc_key_destroy, +}; + +int mmc_register_key_type(void) +{ + return register_key_type(&mmc_key_type); +} + +void mmc_unregister_key_type(void) +{ + unregister_key_type(&mmc_key_type); +} + +static ssize_t +mmc_lockable_show(struct device *dev, struct device_attribute *att, char *buf) +{ + struct mmc_card *card = dev_to_mmc_card(dev); + + return sprintf(buf, "%slocked\n", mmc_card_locked(card) ? "" : "un"); +} + +/* + * implement MMC password functions: force erase, remove password, change + * password, unlock card and assign password. + */ +static ssize_t +mmc_lockable_store(struct device *dev, struct device_attribute *att, + const char *data, size_t len) +{ + struct mmc_card *card = dev_to_mmc_card(dev); + int ret; + struct key *mmc_key; + + if(!mmc_card_lockable(card)) + return -EINVAL; + + mmc_claim_host(card->host); + + ret = -EINVAL; + if (mmc_card_locked(card) && !strncmp(data, "erase", 5)) { + /* forced erase only works while card is locked */ + mmc_lock_unlock(card, NULL, MMC_LOCK_MODE_ERASE); + ret = len; + } else if (!mmc_card_locked(card) && !strncmp(data, "remove", 6)) { + /* remove password only works while card is unlocked */ + mmc_key = request_key(&mmc_key_type, "mmc:key", "remove"); + + if (!IS_ERR(mmc_key)) { + ret = mmc_lock_unlock(card, mmc_key, MMC_LOCK_MODE_CLR_PWD); + if (!ret) + ret = len; + } else + dev_dbg(&card->dev, "request_key returned error %ld\n", PTR_ERR(mmc_key)); + } else if (!mmc_card_locked(card) && ((!strncmp(data, "assign", 6)) || + (!strncmp(data, "change", 6)))) { + /* assign or change */ + if(!(strncmp(data, "assign", 6))) + mmc_key = request_key(&mmc_key_type, "mmc:key", "assign"); + else + mmc_key = request_key(&mmc_key_type, "mmc:key", "change"); + + if (!IS_ERR(mmc_key)) { + ret = mmc_lock_unlock(card, mmc_key, MMC_LOCK_MODE_SET_PWD); + if (!ret) + ret = len; + } else + dev_dbg(&card->dev, "request_key returned error %ld\n", PTR_ERR(mmc_key)); + } else if (mmc_card_locked(card) && !strncmp(data, "unlock", 6)) { + /* unlock */ + mmc_key = request_key(&mmc_key_type, "mmc:key", "unlock"); + if (!IS_ERR(mmc_key)) { + ret = mmc_lock_unlock(card, mmc_key, MMC_LOCK_MODE_UNLOCK); + if (ret) { + dev_dbg(&card->dev, "Wrong password\n"); + ret = -EINVAL; + } + else { + mmc_release_host(card->host); + device_release_driver(dev); + ret = device_attach(dev); + if(!ret) + return -EINVAL; + else + return len; + } + } else + dev_dbg(&card->dev, "request_key returned error %ld\n", PTR_ERR(mmc_key)); + } + + mmc_release_host(card->host); + return ret; +} + +static struct device_attribute mmc_dev_attr_lockable[] = { + __ATTR(lockable, S_IWUSR | S_IRUGO, + mmc_lockable_show, mmc_lockable_store), + __ATTR_NULL, +}; + +int mmc_lock_add_sysfs(struct mmc_card *card) +{ + if (!mmc_card_lockable(card)) + return 0; + + return mmc_add_attrs(card, mmc_dev_attr_lockable); +} + +void mmc_lock_remove_sysfs(struct mmc_card *card) +{ + if (!mmc_card_lockable(card)) + return; + + mmc_remove_attrs(card, mmc_dev_attr_lockable); +} + diff --git a/drivers/mmc/core/lock.h b/drivers/mmc/core/lock.h new file mode 100644 index 0000000..32a0314 --- /dev/null +++ b/drivers/mmc/core/lock.h @@ -0,0 +1,51 @@ +/* + * linux/drivers/mmc/core/lock.h + * + * Copyright 2006 Instituto Nokia de Tecnologia (INdT), All Rights Reserved. + * 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 version 2 as + * published by the Free Software Foundation. + */ +#ifndef _MMC_CORE_LOCK_H +#define _MMC_CORE_LOCK_H + +#ifdef CONFIG_MMC_PASSWORDS + +/* core-internal data */ +struct mmc_key_payload { + struct rcu_head rcu; /* RCU destructor */ + unsigned short datalen; /* length of this data */ + char data[0]; /* actual data */ +}; + +int mmc_register_key_type(void); +void mmc_unregister_key_type(void); + +int mmc_lock_add_sysfs(struct mmc_card *card); +void mmc_lock_remove_sysfs(struct mmc_card *card); + +#else + +static inline int mmc_register_key_type(void) +{ + return 0; +} + +static inline void mmc_unregister_key_type(void) +{ +} + +static inline int mmc_lock_add_sysfs(struct mmc_card *card) +{ + return 0; +} + +static inline void mmc_lock_remove_sysfs(struct mmc_card *card) +{ +} + +#endif + +#endif diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 21d7f48..e10673f 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -19,6 +19,7 @@ #include #include "core.h" #include "sysfs.h" #include "bus.h" +#include "lock.h" #include "mmc_ops.h" static const unsigned int tran_exp[] = { @@ -161,13 +162,14 @@ static int mmc_read_ext_csd(struct mmc_c { int err; u8 *ext_csd; + unsigned int ext_csd_struct; 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 +178,19 @@ static int mmc_read_ext_csd(struct mmc_c 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,18 +205,29 @@ static int mmc_read_ext_csd(struct mmc_c "EXT_CSD, performance might " "suffer.\n", mmc_hostname(card->host)); - err = MMC_ERR_NONE; + err = 0; } + goto out; } - card->ext_csd.sectors = - ext_csd[EXT_CSD_SEC_CNT + 0] << 0 | - ext_csd[EXT_CSD_SEC_CNT + 1] << 8 | - ext_csd[EXT_CSD_SEC_CNT + 2] << 16 | - ext_csd[EXT_CSD_SEC_CNT + 3] << 24; - if (card->ext_csd.sectors) - mmc_card_set_blockaddr(card); + ext_csd_struct = ext_csd[EXT_CSD_REV]; + if (ext_csd_struct > 2) { + printk(KERN_ERR "%s: unrecognised EXT_CSD structure " + "version %d\n", mmc_hostname(card->host), + ext_csd_struct); + return -EINVAL; + } + + if (ext_csd_struct >= 2) { + card->ext_csd.sectors = + ext_csd[EXT_CSD_SEC_CNT + 0] << 0 | + ext_csd[EXT_CSD_SEC_CNT + 1] << 8 | + ext_csd[EXT_CSD_SEC_CNT + 2] << 16 | + ext_csd[EXT_CSD_SEC_CNT + 3] << 24; + if (card->ext_csd.sectors) + mmc_card_set_blockaddr(card); + } switch (ext_csd[EXT_CSD_CARD_TYPE]) { case EXT_CSD_CARD_TYPE_52 | EXT_CSD_CARD_TYPE_26: @@ -244,6 +263,7 @@ static int mmc_init_card(struct mmc_host int err; u32 cid[4]; unsigned int max_dtr; + u32 status; BUG_ON(!host); BUG_ON(!host->claimed); @@ -258,19 +278,21 @@ static int mmc_init_card(struct mmc_host /* 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 +300,10 @@ static int mmc_init_card(struct mmc_host * 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,24 +314,33 @@ static int mmc_init_card(struct mmc_host * 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); + /* + * Check if card is locked. + */ + err = mmc_send_status(card, &status); + if (err) + goto free_card; + if (status & R1_CARD_IS_LOCKED) + mmc_card_set_locked(card); + if (!oldcard) { /* * 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 +348,7 @@ static int mmc_init_card(struct mmc_host * 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 +356,7 @@ static int mmc_init_card(struct mmc_host * Fetch and process extened CSD. */ err = mmc_read_ext_csd(card); - if (err != MMC_ERR_NONE) + if (err) goto free_card; } @@ -334,7 +367,7 @@ static int mmc_init_card(struct mmc_host (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 +396,7 @@ static int mmc_init_card(struct mmc_host (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 +405,14 @@ static int mmc_init_card(struct mmc_host 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 +446,7 @@ static void mmc_detect(struct mmc_host * mmc_release_host(host); - if (err != MMC_ERR_NONE) { + if (err) { mmc_remove(host); mmc_claim_host(host); @@ -458,6 +491,12 @@ static int mmc_sysfs_add(struct mmc_host if (ret < 0) return ret; + ret = mmc_lock_add_sysfs(card); + if (ret < 0) { + mmc_remove_attrs(card, mmc_dev_attrs); + return ret; + } + return 0; } @@ -466,6 +505,7 @@ static int mmc_sysfs_add(struct mmc_host */ static void mmc_sysfs_remove(struct mmc_host *host, struct mmc_card *card) { + mmc_lock_remove_sysfs(card); mmc_remove_attrs(card, mmc_dev_attrs); } @@ -481,7 +521,7 @@ static void mmc_suspend(struct mmc_host mmc_claim_host(host); mmc_deselect_cards(host); - host->card->state &= ~MMC_STATE_HIGHSPEED; + host->card->state &= ~(MMC_STATE_HIGHSPEED | MMC_STATE_LOCKED); mmc_release_host(host); } @@ -502,7 +542,7 @@ static void mmc_resume(struct mmc_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); @@ -565,7 +605,7 @@ int mmc_attach_mmc(struct mmc_host *host * 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 +627,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 913e75f..b8afebe 100644 --- a/drivers/mmc/core/mmc_ops.c +++ b/drivers/mmc/core/mmc_ops.c @@ -2,6 +2,8 @@ * linux/drivers/mmc/core/mmc_ops.h * * Copyright 2006-2007 Pierre Ossman + * MMC password protection (C) 2006 Instituto Nokia de Tecnologia (INdT), + * All Rights Reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -12,12 +14,14 @@ #include #include #include +#include #include #include #include #include "core.h" +#include "lock.h" #include "mmc_ops.h" static int _mmc_select_card(struct mmc_host *host, struct mmc_card *card) @@ -40,10 +44,10 @@ static int _mmc_select_card(struct mmc_h } 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 +103,13 @@ int mmc_send_op_cond(struct mmc_host *ho 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 +135,12 @@ int mmc_all_send_cid(struct mmc_host *ho 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 +158,10 @@ int mmc_set_relative_addr(struct mmc_car 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 +180,12 @@ int mmc_send_csd(struct mmc_card *card, 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) @@ -214,16 +218,16 @@ int mmc_send_ext_csd(struct mmc_card *ca sg_init_one(&sg, ext_csd, 512); - mmc_set_data_timeout(&data, card, 0); + mmc_set_data_timeout(&data, card); 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 +248,10 @@ int mmc_switch(struct mmc_card *card, u8 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 +269,127 @@ int mmc_send_status(struct mmc_card *car 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; } +#ifdef CONFIG_MMC_PASSWORDS + +int mmc_lock_unlock(struct mmc_card *card, struct key *key, int mode) +{ + struct mmc_request mrq; + struct mmc_command cmd; + struct mmc_data data; + struct scatterlist sg; + struct mmc_key_payload *mpayload; + unsigned long erase_timeout; + int err, data_size; + u8 *data_buf; + + mpayload = NULL; + data_size = 1; + if (!(mode & MMC_LOCK_MODE_ERASE)) { + mpayload = rcu_dereference(key->payload.data); + data_size = 2 + mpayload->datalen; + } + + data_buf = kmalloc(data_size, GFP_KERNEL); + if (!data_buf) + return -ENOMEM; + memset(data_buf, 0, data_size); + + data_buf[0] |= mode; + if (mode & MMC_LOCK_MODE_UNLOCK) + data_buf[0] &= ~MMC_LOCK_MODE_UNLOCK; + + if (!(mode & MMC_LOCK_MODE_ERASE)) { + data_buf[1] = mpayload->datalen; + memcpy(data_buf + 2, mpayload->data, mpayload->datalen); + } + + memset(&cmd, 0, sizeof(struct mmc_command)); + + cmd.opcode = MMC_SET_BLOCKLEN; + cmd.arg = data_size; + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + err = mmc_wait_for_cmd(card->host, &cmd, MMC_CMD_RETRIES); + if (err) + goto out; + + memset(&cmd, 0, sizeof(struct mmc_command)); + + cmd.opcode = MMC_LOCK_UNLOCK; + cmd.arg = 0; + cmd.flags = MMC_RSP_R1B | MMC_CMD_ADTC; + + memset(&data, 0, sizeof(struct mmc_data)); + + data.blksz = data_size; + data.blocks = 1; + data.flags = MMC_DATA_WRITE; + data.sg = &sg; + data.sg_len = 1; + + mmc_set_data_timeout(&data, card); + + memset(&mrq, 0, sizeof(struct mmc_request)); + + mrq.cmd = &cmd; + mrq.data = &data; + + sg_init_one(&sg, data_buf, data_size); + mmc_wait_for_req(card->host, &mrq); + err = cmd.error; + if (err) + goto out; + err = data.error; + if (err) + goto out; + + memset(&cmd, 0, sizeof(struct mmc_command)); + + cmd.opcode = MMC_SEND_STATUS; + cmd.arg = card->rca << 16; + cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + + /* set timeout for forced erase operation to 3 min. (see MMC spec) */ + erase_timeout = jiffies + 180 * HZ; + do { + /* we cannot use "retries" here because the + * R1_LOCK_UNLOCK_FAILED bit is cleared by subsequent reads to + * the status register, hiding the error condition */ + err = mmc_wait_for_cmd(card->host, &cmd, 0); + if (err) + break; + /* the other modes don't need timeout checking */ + if (!(mode & MMC_LOCK_MODE_ERASE)) + continue; + if (time_after(jiffies, erase_timeout)) { + dev_dbg(&card->dev, "forced erase timed out\n"); + err = -ETIMEDOUT; + break; + } + } while (!(cmd.resp[0] & R1_READY_FOR_DATA)); + if (cmd.resp[0] & R1_LOCK_UNLOCK_FAILED) { + dev_dbg(&card->dev, "LOCK_UNLOCK operation failed\n"); + err = -EIO; + } + + if (cmd.resp[0] & R1_CARD_IS_LOCKED) + mmc_card_set_locked(card); + else + card->state &= ~MMC_STATE_LOCKED; + +out: + kfree(data_buf); + + return err; +} + +#endif /* CONFIG_MMC_PASSWORDS */ + diff --git a/drivers/mmc/core/mmc_ops.h b/drivers/mmc/core/mmc_ops.h index 76d09a9..28671bf 100644 --- a/drivers/mmc/core/mmc_ops.h +++ b/drivers/mmc/core/mmc_ops.h @@ -12,6 +12,8 @@ #ifndef _MMC_MMC_OPS_H #define _MMC_MMC_OPS_H +struct key; + int mmc_select_card(struct mmc_card *card); int mmc_deselect_cards(struct mmc_host *host); int mmc_go_idle(struct mmc_host *host); @@ -22,6 +24,7 @@ int mmc_send_csd(struct mmc_card *card, int mmc_send_ext_csd(struct mmc_card *card, u8 *ext_csd); int mmc_switch(struct mmc_card *card, u8 set, u8 index, u8 value); int mmc_send_status(struct mmc_card *card, u32 *status); +int mmc_lock_unlock(struct mmc_card *card, struct key *key, int mode); #endif diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 1edc62b..71628ae 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -20,6 +20,7 @@ #include #include "core.h" #include "sysfs.h" #include "bus.h" +#include "lock.h" #include "mmc_ops.h" #include "sd_ops.h" @@ -193,30 +194,38 @@ static int mmc_read_switch(struct mmc_ca 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 +247,28 @@ static int mmc_switch_hs(struct mmc_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) { @@ -290,6 +299,7 @@ static int mmc_sd_init_card(struct mmc_h int err; u32 cid[4]; unsigned int max_dtr; + u32 status; BUG_ON(!host); BUG_ON(!host->claimed); @@ -309,23 +319,25 @@ static int mmc_sd_init_card(struct mmc_h * 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 +345,10 @@ static int mmc_sd_init_card(struct mmc_h * 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,21 +358,30 @@ static int mmc_sd_init_card(struct mmc_h * 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); + /* + * Check if card is locked. + */ + err = mmc_send_status(card, &status); + if (err) + goto free_card; + if (status & R1_CARD_IS_LOCKED) + mmc_card_set_locked(card); + if (!oldcard) { /* * 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 +391,7 @@ static int mmc_sd_init_card(struct mmc_h * 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 +399,7 @@ static int mmc_sd_init_card(struct mmc_h * 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 +410,7 @@ static int mmc_sd_init_card(struct mmc_h * Fetch switch information from card. */ err = mmc_read_switch(card); - if (err != MMC_ERR_NONE) + if (err) goto free_card; } @@ -395,7 +418,7 @@ static int mmc_sd_init_card(struct mmc_h * 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 +441,7 @@ static int mmc_sd_init_card(struct mmc_h 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 +465,14 @@ static int mmc_sd_init_card(struct mmc_h 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 +506,7 @@ static void mmc_sd_detect(struct mmc_hos mmc_release_host(host); - if (err != MMC_ERR_NONE) { + if (err) { mmc_sd_remove(host); mmc_claim_host(host); @@ -530,6 +553,12 @@ static int mmc_sd_sysfs_add(struct mmc_h if (ret < 0) return ret; + ret = mmc_lock_add_sysfs(card); + if (ret < 0) { + mmc_remove_attrs(card, mmc_sd_dev_attrs); + return ret; + } + return 0; } @@ -538,6 +567,7 @@ static int mmc_sd_sysfs_add(struct mmc_h */ static void mmc_sd_sysfs_remove(struct mmc_host *host, struct mmc_card *card) { + mmc_lock_remove_sysfs(card); mmc_remove_attrs(card, mmc_sd_dev_attrs); } @@ -553,7 +583,7 @@ static void mmc_sd_suspend(struct mmc_ho mmc_claim_host(host); mmc_deselect_cards(host); - host->card->state &= ~MMC_STATE_HIGHSPEED; + host->card->state &= ~(MMC_STATE_HIGHSPEED | MMC_STATE_LOCKED); mmc_release_host(host); } @@ -574,7 +604,7 @@ static void mmc_sd_resume(struct mmc_hos 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); @@ -644,7 +674,7 @@ int mmc_attach_sd(struct mmc_host *host, * 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 +696,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 342f340..491e030 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 * } 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 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 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 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_car 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 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 *ho 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_ho 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 *ca 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)); @@ -252,19 +252,19 @@ int mmc_app_send_scr(struct mmc_card *ca sg_init_one(&sg, scr, 8); - mmc_set_data_timeout(&data, card, 0); + mmc_set_data_timeout(&data, card); 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, @@ -302,15 +302,15 @@ int mmc_sd_switch(struct mmc_card *card, sg_init_one(&sg, resp, 64); - mmc_set_data_timeout(&data, card, 0); + mmc_set_data_timeout(&data, card); 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 0000000..7c0dc0c --- /dev/null +++ b/drivers/mmc/core/sdio.c @@ -0,0 +1,336 @@ +/* + * 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 + +#include +#include +#include +#include + +#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, + func->num * 0x100 + SDIO_FBR_STD_IF, 0, &data); + if (ret) + goto out; + + data &= 0x0f; + + if (data == 0x0f) { + ret = mmc_io_rw_direct(func->card, 0, 0, + func->num * 0x100 + 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_cis(func, fn); + 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; +} + +/* + * 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); + BUG_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; + + /* + * 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 0000000..4e284f5 --- /dev/null +++ b/drivers/mmc/core/sdio_bus.c @@ -0,0 +1,251 @@ +/* + * 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 +#include + +#include +#include + +#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; + + id = sdio_match_device(func, drv); + if (!id) + return -ENODEV; + + 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); + + 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); + + 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 = kmalloc(sizeof(struct sdio_func), GFP_KERNEL); + if (!func) + return ERR_PTR(-ENOMEM); + + memset(func, 0, sizeof(struct sdio_func)); + + 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 0000000..567a768 --- /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 0000000..9cf827b --- /dev/null +++ b/drivers/mmc/core/sdio_cis.c @@ -0,0 +1,286 @@ +/* + * linux/drivers/mmc/core/sdio_cis.c + * + * 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. + */ + +#include + +#include +#include +#include +#include + +#include "sdio_cis.h" +#include "sdio_ops.h" + + +static int cistpl_vers_1(struct sdio_func *func, unsigned fn, + const unsigned char *buf, unsigned size) +{ + unsigned i, nr_strings = 0; + + printk(KERN_DEBUG "%s: CISTPL_VERS_1 = %u.%u\n", + sdio_func_id(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_func_id(func)); + for (i = 0; i < nr_strings; i++) { + printk(" \"%s\"", buf); + while (*buf++); + } + printk("\n"); + + return 0; +} + +static int cistpl_manfid(struct sdio_func *func, unsigned fn, + const unsigned char *buf, unsigned size) +{ + /* TPLMID_MANF */ + func->vendor = buf[0] | (buf[1] << 8); + printk(KERN_DEBUG "%s: TPLMID_MANF = 0x%04x\n", + sdio_func_id(func), func->vendor); + + /* TPLMID_CARD */ + func->device = buf[2] | (buf[3] << 8); + printk(KERN_DEBUG "%s: TPLMID_CARD = 0x%04x\n", + sdio_func_id(func), func->device); + + return 0; +} + +static int cistpl_funcid(struct sdio_func *func, unsigned fn, + const unsigned char *buf, unsigned size) +{ + /* TPLFID_FUNCTION */ + printk(KERN_DEBUG "%s: TPLFID_FUNCTION = 0x%02x\n", + sdio_func_id(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(struct sdio_func *func, unsigned fn, + const unsigned char *buf, unsigned size) +{ + unsigned val; + + /* + * 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 (fn == 0) { + if (size < 0x04 || buf[0] != 0) + goto bad; + /* TPLFE_FN0_BLK_SIZE */ + val = buf[1] | (buf[2] << 8); + printk(KERN_DEBUG "%s: TPLFE_FN0_BLK_SIZE = %u\n", + sdio_func_id(func), val); + /* TPLFE_MAX_TRAN_SPEED */ + val = speed_val[(buf[3] >> 3) & 15] * speed_unit[buf[3] & 7]; + printk(KERN_DEBUG "%s: max speed = %u kbps\n", + sdio_func_id(func), val/1000); + } else { + unsigned vsn = func->card->cccr.sdio_vsn; + unsigned min_size = (vsn == SDIO_SDIO_REV_1_00) ? 28 : 42; + if (size < min_size || buf[0] != 1) + goto bad; + /* 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 */ + val = buf[12] | (buf[13] << 8); + printk(KERN_DEBUG "%s: TPLFE_MAX_BLK_SIZE = %u\n", + sdio_func_id(func), val); + /* 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; + +bad: + printk(KERN_WARNING "%s: bad CISTPL_FUNCE size %u type %u for function %u\n", + sdio_func_id(func), size, buf[0], fn); + return -EINVAL; +} + +typedef int (tpl_parse_t)(struct sdio_func *, unsigned, + 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 }, +}; + +int sdio_read_cis(struct sdio_func *func, unsigned int fn) +{ + int ret; + struct sdio_func_tuple *this, **prev; + unsigned i, ptr = 0; + + /* + * The common CIS provides information global to all functions, + * and the function specific CIS is allowed to override the + * common CIS. Therefore, we always read the common CIS first. + */ + if (fn != 0) { + ret = sdio_read_cis(func, 0); + if (ret) + return ret; + } + + /* + * 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; + ret = mmc_io_rw_direct(func->card, 0, 0, + fn * 0x100 + SDIO_FBR_CIS + i, 0, &x); + if (ret) + return ret; + ptr |= x << (i * 8); + } + + /* find the list tail */ + for (prev = &func->tuples; *prev; prev = &(*prev)->next); + + do { + unsigned char tpl_code, tpl_link; + + ret = mmc_io_rw_direct(func->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(func->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(func->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_func_id(func), tpl_code, tpl_link); + } else { + const struct cis_tpl *tpl = cis_tpl_list + i; + if (tpl_link < tpl->min_size) { + printk(KERN_WARNING + "%s: bad CIS tuple 0x%02x (length = %u, expected >= %u\n", + sdio_func_id(func), tpl_code, tpl_link, tpl->min_size); + ret = -EINVAL; + } else + ret = tpl->parse(func, fn, this->data, tpl_link); + kfree(this); + } + + ptr += tpl_link; + } while (!ret); + + return ret; +} diff --git a/drivers/mmc/core/sdio_cis.h b/drivers/mmc/core/sdio_cis.h new file mode 100644 index 0000000..d515209 --- /dev/null +++ b/drivers/mmc/core/sdio_cis.h @@ -0,0 +1,19 @@ +/* + * 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_cis(struct sdio_func *func, unsigned int fn); + +#endif diff --git a/drivers/mmc/core/sdio_io.c b/drivers/mmc/core/sdio_io.c new file mode 100644 index 0000000..7f08ba5 --- /dev/null +++ b/drivers/mmc/core/sdio_io.c @@ -0,0 +1,395 @@ +/* + * 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 +#include +#include +#include + +#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_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 up to 512 bytes 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 mmc_io_rw_extended(func->card, 0, func->num, addr, 0, 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 up to 512 bytes 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 mmc_io_rw_extended(func->card, 1, func->num, addr, 0, 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 up to 512 bytes 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 mmc_io_rw_extended(func->card, 0, func->num, addr, 1, 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 up to 512 bytes 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 mmc_io_rw_extended(func->card, 1, func->num, addr, 1, 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; + unsigned short value; + + if (err_ret) + *err_ret = 0; + + ret = sdio_memcpy_fromio(func, &value, addr, 2); + if (ret) { + if (err_ret) + *err_ret = ret; + return 0xFFFF; + } + + return le16_to_cpu(value); +} + +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; + + b = cpu_to_le16(b); + + ret = sdio_memcpy_toio(func, addr, &b, 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; + unsigned long value; + + if (err_ret) + *err_ret = 0; + + ret = sdio_memcpy_fromio(func, &value, addr, 4); + if (ret) { + if (err_ret) + *err_ret = ret; + return 0xFFFFFFFF; + } + + return le32_to_cpu(value); +} + +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; + + b = cpu_to_le32(b); + + ret = sdio_memcpy_toio(func, addr, &b, 4); + if (err_ret) + *err_ret = ret; +} + +EXPORT_SYMBOL_GPL(sdio_writel); + diff --git a/drivers/mmc/core/sdio_irq.c b/drivers/mmc/core/sdio_irq.c new file mode 100644 index 0000000..a58a647 --- /dev/null +++ b/drivers/mmc/core/sdio_irq.c @@ -0,0 +1,235 @@ +/* + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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->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); + 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->disable_sdio_irq(host); + + 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; + + BUG_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, "sdio_irqd"); + 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; + + BUG_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; + + 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); + + 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 0000000..4f2c771 --- /dev/null +++ b/drivers/mmc/core/sdio_ops.c @@ -0,0 +1,143 @@ +/* + * 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 +#include + +#include +#include +#include +#include + +#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 bang, u8 *buf, unsigned size) +{ + struct mmc_request mrq; + struct mmc_command cmd; + struct mmc_data data; + struct scatterlist sg; + + BUG_ON(!card); + BUG_ON(fn > 7); + BUG_ON(size > 512); + + 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 |= bang ? 0x00000000 : 0x04000000; + cmd.arg |= addr << 9; + cmd.arg |= (size == 512) ? 0 : size; + cmd.flags = MMC_RSP_R5 | MMC_CMD_ADTC; + + data.blksz = size; + data.blocks = 1; + data.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ; + data.sg = &sg; + data.sg_len = 1; + + sg_init_one(&sg, buf, size); + + mmc_set_data_timeout(&data, card); + + 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 0000000..1d42e4f --- /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 bang, u8 *data, unsigned size); + +#endif + diff --git a/drivers/mmc/host/at91_mci.c b/drivers/mmc/host/at91_mci.c index 62564cc..35a640d 100644 --- a/drivers/mmc/host/at91_mci.c +++ b/drivers/mmc/host/at91_mci.c @@ -328,7 +328,7 @@ static void at91_mci_handle_transmitted( data = cmd->data; if (!data) return; - if (cmd->data->flags & MMC_DATA_MULTI) { + if (cmd->data->blocks > 1) { pr_debug("multiple write : wait for BLKE...\n"); at91_mci_write(host, AT91_MCI_IER, AT91_MCI_BLKE); } else @@ -439,7 +439,7 @@ static void at91_mci_send_command(struct if (data->flags & MMC_DATA_STREAM) cmdr |= AT91_MCI_TRTYP_STREAM; - if (data->flags & MMC_DATA_MULTI) + if (data->blocks > 1) cmdr |= AT91_MCI_TRTYP_MULTIPLE; } else { @@ -577,24 +577,22 @@ static void at91_mci_completed_command(s 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); } @@ -836,7 +834,6 @@ static int __init at91_mci_probe(struct mmc->f_min = 375000; mmc->f_max = 25000000; mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; - mmc->caps = MMC_CAP_BYTEBLOCK; mmc->max_blk_size = 4095; mmc->max_blk_count = mmc->max_req_size; diff --git a/drivers/mmc/host/au1xmmc.c b/drivers/mmc/host/au1xmmc.c index 34c99d4..92c4d0d 100644 --- a/drivers/mmc/host/au1xmmc.c +++ b/drivers/mmc/host/au1xmmc.c @@ -186,7 +186,7 @@ static void au1xmmc_tasklet_finish(unsig } static int au1xmmc_send_command(struct au1xmmc_host *host, int wait, - struct mmc_command *cmd, unsigned int flags) + struct mmc_command *cmd, struct mmc_data *data) { u32 mmccmd = (cmd->opcode << SD_CMD_CI_SHIFT); @@ -208,19 +208,21 @@ static int au1xmmc_send_command(struct a 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) { - if (flags & MMC_DATA_MULTI) - mmccmd |= SD_CMD_CT_4; - else - mmccmd |= SD_CMD_CT_2; - } else if (flags & MMC_DATA_WRITE) { - if (flags & MMC_DATA_MULTI) - mmccmd |= SD_CMD_CT_3; - else - mmccmd |= SD_CMD_CT_1; + if (data) { + if (flags & MMC_DATA_READ) { + if (data->blocks > 1) + mmccmd |= SD_CMD_CT_4; + else + mmccmd |= SD_CMD_CT_2; + } else if (flags & MMC_DATA_WRITE) { + if (data->blocks > 1) + mmccmd |= SD_CMD_CT_3; + else + mmccmd |= SD_CMD_CT_1; + } } au_writel(cmd->arg, HOST_CMDARG(host)); @@ -253,7 +255,7 @@ static int au1xmmc_send_command(struct a 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 +280,7 @@ static void au1xmmc_data_complete(struct 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 +290,14 @@ static void au1xmmc_data_complete(struct 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 +477,7 @@ static void au1xmmc_cmd_complete(struct 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 +514,11 @@ static void au1xmmc_cmd_complete(struct /* 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 +591,7 @@ au1xmmc_prepare_data(struct au1xmmc_host 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 +642,11 @@ au1xmmc_prepare_data(struct au1xmmc_host //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 +658,7 @@ static void au1xmmc_request(struct mmc_h 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 +674,10 @@ static void au1xmmc_request(struct mmc_h ret = au1xmmc_prepare_data(host, mrq->data); } - if (ret == MMC_ERR_NONE) - ret = au1xmmc_send_command(host, 0, mrq->cmd, flags); + if (!ret) + ret = au1xmmc_send_command(host, 0, mrq->cmd, mrq->data); - if (ret != MMC_ERR_NONE) { + if (ret) { mrq->cmd->error = ret; au1xmmc_finish_request(host); } @@ -764,10 +766,10 @@ static irqreturn_t au1xmmc_irq(int irq, 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 54bfc9f..e33c123 100644 --- a/drivers/mmc/host/imxmmc.c +++ b/drivers/mmc/host/imxmmc.c @@ -428,11 +428,11 @@ static int imxmci_finish_data(struct imx 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 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 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 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); @@ -963,7 +963,7 @@ static int imxmci_probe(struct platform_ mmc->f_min = 150000; mmc->f_max = CLK_RATE/2; mmc->ocr_avail = MMC_VDD_32_33; - mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_BYTEBLOCK; + mmc->caps = MMC_CAP_4_BIT_DATA; /* MMC core transfer sizes tunable parameters */ mmc->max_hw_segs = 64; diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index be730c0..7d93868 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -154,11 +154,11 @@ mmci_data_irq(struct mmci_host *host, st } 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, str 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); @@ -391,6 +391,14 @@ static void mmci_request(struct mmc_host WARN_ON(host->mrq != NULL); + if (mrq->data && hweight32(mrq->data->blksz)) { + printk(KERN_ERR "%s: Unsupported block size (%d bytes)\n", + mmc_hostname(mmc), mrq->data->blksz); + mrq->cmd->error = -EINVAL; + mmc_end_request(mmc, mrq); + return; + } + spin_lock_irq(&host->lock); host->mrq = mrq; diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 0cf97ed..60a67df 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 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 * } } - 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 @@ #endif 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 @@ #endif 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 @@ #endif 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 ff96033..b89e32d 100644 --- a/drivers/mmc/host/pxamci.c +++ b/drivers/mmc/host/pxamci.c @@ -226,7 +226,7 @@ static int pxamci_cmd_done(struct pxamci } 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 @@ #ifdef CONFIG_PXA27x 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 pxamc 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 pxamc * 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 f2bc87a..244a08c 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -477,17 +477,17 @@ static void sdhci_finish_data(struct sdh /* * 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 sdh * 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 sd 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 sd 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 } } - 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_hos 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); @@ -827,7 +827,7 @@ static void sdhci_tasklet_card(unsigned 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 +855,9 @@ static void sdhci_tasklet_finish(unsigne * 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 +902,13 @@ static void sdhci_timeout_timer(unsigned 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 +937,12 @@ static void sdhci_cmd_irq(struct sdhci_h } 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 +969,11 @@ static void sdhci_data_irq(struct sdhci_ } 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)) @@ -1298,7 +1295,7 @@ static int __devinit sdhci_probe_slot(st 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; 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 8b736e9..4afeb79 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 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 ti { 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 ti 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, @@ -626,14 +626,21 @@ static void tifm_sd_request(struct mmc_h spin_lock_irqsave(&sock->lock, flags); if (host->eject) { - spin_unlock_irqrestore(&sock->lock, flags); + mrq->cmd->error = -ENOMEDIUM; goto err_out; } if (host->req) { printk(KERN_ERR "%s : unfinished request detected\n", sock->dev.bus_id); - spin_unlock_irqrestore(&sock->lock, flags); + mrq->cmd->error = -ETIMEDOUT; + goto err_out; + } + + if (mrq->data && hweight32(mrq->data->blksz)) { + printk(KERN_ERR "%s: Unsupported block size (%d bytes)\n", + sock->dev.bus_id, mrq->data->blksz); + mrq->cmd->error = -EINVAL; goto err_out; } @@ -722,7 +729,7 @@ static void tifm_sd_request(struct mmc_h return; err_out: - mrq->cmd->error = MMC_ERR_TIMEOUT; + spin_unlock_irqrestore(&sock->lock, flags); mmc_request_done(mmc, mrq); } @@ -1012,9 +1019,9 @@ static void tifm_sd_remove(struct tifm_d 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 e0c9808..a8642e3 100644 --- a/drivers/mmc/host/wbsd.c +++ b/drivers/mmc/host/wbsd.c @@ -317,7 +317,7 @@ static inline void wbsd_get_short_reply( * 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(s * 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 wbs 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 wbs /* 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 wbs ((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 wbs */ 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 wbs } } - 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 "%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 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 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 @@ #ifdef CONFIG_MMC_DEBUG "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 @@ #endif 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 @@ #endif * 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 l 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 lo 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(unsigne DBGF("Timeout\n"); - data->error = MMC_ERR_TIMEOUT; + data->error = -ETIMEDOUT; tasklet_schedule(&host->finish_tasklet); @@ -1220,7 +1219,7 @@ static int __devinit wbsd_alloc_mmc(stru mmc->f_min = 375000; mmc->f_max = 24000000; mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; - mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_MULTIWRITE | MMC_CAP_BYTEBLOCK; + mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_MULTIWRITE; spin_lock_init(&host->lock); diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index badf702..b5c4f97 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -55,7 +55,20 @@ struct sd_switch_caps { unsigned int hs_max_dtr; }; +struct sdio_cccr { + unsigned int sdio_vsn; + unsigned int sd_vsn; + unsigned int multi_block:1, + low_speed:1, + wide_bus:1, + high_power:1, + high_speed:1; +}; + struct mmc_host; +struct sdio_func; + +#define SDIO_MAX_FUNCS 7 /* * MMC device @@ -67,11 +80,14 @@ struct mmc_card { unsigned int type; /* card type */ #define MMC_TYPE_MMC 0 /* MMC card */ #define MMC_TYPE_SD 1 /* SD card */ +#define MMC_TYPE_SDIO 2 /* SDIO card */ unsigned int state; /* (our) card state */ #define MMC_STATE_PRESENT (1<<0) /* present in sysfs */ #define MMC_STATE_READONLY (1<<1) /* card is read-only */ #define MMC_STATE_HIGHSPEED (1<<2) /* card is in high speed mode */ #define MMC_STATE_BLOCKADDR (1<<3) /* card uses block-addressing */ +#define MMC_STATE_LOCKED (1<<4) /* card is currently locked */ + u32 raw_cid[4]; /* raw card CID */ u32 raw_csd[4]; /* raw card CSD */ u32 raw_scr[2]; /* raw card SCR */ @@ -80,20 +96,29 @@ #define MMC_STATE_BLOCKADDR (1<<3) /* c struct mmc_ext_csd ext_csd; /* mmc v4 extended card specific */ struct sd_scr scr; /* extra SD information */ struct sd_switch_caps sw_caps; /* switch (CMD6) caps */ + + unsigned int sdio_funcs; /* number of SDIO functions */ + struct sdio_cccr cccr; /* common card info */ + struct sdio_func *sdio_func[SDIO_MAX_FUNCS]; /* SDIO functions (devices) */ }; #define mmc_card_mmc(c) ((c)->type == MMC_TYPE_MMC) #define mmc_card_sd(c) ((c)->type == MMC_TYPE_SD) +#define mmc_card_sdio(c) ((c)->type == MMC_TYPE_SDIO) #define mmc_card_present(c) ((c)->state & MMC_STATE_PRESENT) #define mmc_card_readonly(c) ((c)->state & MMC_STATE_READONLY) #define mmc_card_highspeed(c) ((c)->state & MMC_STATE_HIGHSPEED) #define mmc_card_blockaddr(c) ((c)->state & MMC_STATE_BLOCKADDR) +#define mmc_card_locked(c) ((c)->state & MMC_STATE_LOCKED) + +#define mmc_card_lockable(c) ((c)->csd.cmdclass & CCC_LOCK_CARD) #define mmc_card_set_present(c) ((c)->state |= MMC_STATE_PRESENT) #define mmc_card_set_readonly(c) ((c)->state |= MMC_STATE_READONLY) #define mmc_card_set_highspeed(c) ((c)->state |= MMC_STATE_HIGHSPEED) #define mmc_card_set_blockaddr(c) ((c)->state |= MMC_STATE_BLOCKADDR) +#define mmc_card_set_locked(c) ((c)->state |= MMC_STATE_LOCKED) #define mmc_card_name(c) ((c)->cid.prod_name) #define mmc_card_id(c) ((c)->dev.bus_id) diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 63a80ea..8945da9 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -41,6 +41,8 @@ #define MMC_RSP_R1 (MMC_RSP_PRESENT|MMC_ #define MMC_RSP_R1B (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE|MMC_RSP_BUSY) #define MMC_RSP_R2 (MMC_RSP_PRESENT|MMC_RSP_136|MMC_RSP_CRC) #define MMC_RSP_R3 (MMC_RSP_PRESENT) +#define MMC_RSP_R4 (MMC_RSP_PRESENT) +#define MMC_RSP_R5 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE) #define MMC_RSP_R6 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE) #define MMC_RSP_R7 (MMC_RSP_PRESENT|MMC_RSP_CRC|MMC_RSP_OPCODE) @@ -54,12 +56,19 @@ #define mmc_cmd_type(cmd) ((cmd)->flags unsigned int retries; /* max number of retries */ unsigned int error; /* command error */ -#define MMC_ERR_NONE 0 -#define MMC_ERR_TIMEOUT 1 -#define MMC_ERR_BADCRC 2 -#define MMC_ERR_FIFO 3 -#define MMC_ERR_FAILED 4 -#define MMC_ERR_INVALID 5 +/* + * Standard errno values are used for errors, but some have specific + * meaning in the MMC layer: + * + * ETIMEDOUT Card took too long to respond + * EILSEQ Basic format problem with the received or sent data + * (e.g. CRC check failed, incorrect opcode in response + * or bad end bit) + * EINVAL Request cannot be performed because of restrictions + * in hardware and/or the driver + * ENOMEDIUM Host can determine that the slot is empty and is + * actively failing requests + */ struct mmc_data *data; /* data segment associated with cmd */ struct mmc_request *mrq; /* associated request */ @@ -76,7 +85,6 @@ struct mmc_data { #define MMC_DATA_WRITE (1 << 8) #define MMC_DATA_READ (1 << 9) #define MMC_DATA_STREAM (1 << 10) -#define MMC_DATA_MULTI (1 << 11) unsigned int bytes_xfered; @@ -104,9 +112,20 @@ extern int mmc_wait_for_cmd(struct mmc_h extern int mmc_wait_for_app_cmd(struct mmc_host *, struct mmc_card *, struct mmc_command *, int); -extern void mmc_set_data_timeout(struct mmc_data *, const struct mmc_card *, int); +extern void mmc_set_data_timeout(struct mmc_data *, const struct mmc_card *); -extern void mmc_claim_host(struct mmc_host *host); +extern int __mmc_claim_host(struct mmc_host *host, atomic_t *abort); extern void mmc_release_host(struct mmc_host *host); +/** + * mmc_claim_host - exclusively claim a host + * @host: mmc host to claim + * + * Claim a host for a set of operations. + */ +static inline void mmc_claim_host(struct mmc_host *host) +{ + __mmc_claim_host(host, NULL); +} + #endif diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index b1350df..4ce30b6 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -51,6 +51,8 @@ struct mmc_host_ops { void (*request)(struct mmc_host *host, struct mmc_request *req); void (*set_ios)(struct mmc_host *host, struct mmc_ios *ios); int (*get_ro)(struct mmc_host *host); + int (*enable_sdio_irq)(struct mmc_host *host); + int (*disable_sdio_irq)(struct mmc_host *host); }; struct mmc_card; @@ -87,9 +89,9 @@ #define MMC_VDD_35_36 0x00800000 /* VDD #define MMC_CAP_4_BIT_DATA (1 << 0) /* Can the host do 4 bit transfers */ #define MMC_CAP_MULTIWRITE (1 << 1) /* Can accurately report bytes sent to card on error */ -#define MMC_CAP_BYTEBLOCK (1 << 2) /* Can do non-log2 block sizes */ -#define MMC_CAP_MMC_HIGHSPEED (1 << 3) /* Can do MMC high-speed timing */ -#define MMC_CAP_SD_HIGHSPEED (1 << 4) /* Can do SD high-speed timing */ +#define MMC_CAP_MMC_HIGHSPEED (1 << 2) /* Can do MMC high-speed timing */ +#define MMC_CAP_SD_HIGHSPEED (1 << 3) /* Can do SD high-speed timing */ +#define MMC_CAP_SDIO_IRQ (1 << 4) /* Can signal pending SDIO IRQs */ /* host specific block data */ unsigned int max_seg_size; /* see blk_queue_max_segment_size */ @@ -124,6 +126,10 @@ #endif unsigned int bus_refs; /* reference counter */ unsigned int bus_dead:1; /* bus has been released */ + unsigned int sdio_irqs; + struct task_struct *sdio_irq_thread; + atomic_t sdio_irq_thread_abort; + unsigned long private[0] ____cacheline_aligned; }; @@ -147,5 +153,11 @@ extern int mmc_resume_host(struct mmc_ho extern void mmc_detect_change(struct mmc_host *, unsigned long delay); extern void mmc_request_done(struct mmc_host *, struct mmc_request *); +static inline void mmc_signal_sdio_irq(struct mmc_host *host) +{ + host->ops->disable_sdio_irq(host); + wake_up_process(host->sdio_irq_thread); +} + #endif diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index e3ed9b9..cfcabc8 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -227,6 +227,7 @@ #define CSD_SPEC_VER_4 4 #define EXT_CSD_BUS_WIDTH 183 /* R/W */ #define EXT_CSD_HS_TIMING 185 /* R/W */ #define EXT_CSD_CARD_TYPE 196 /* RO */ +#define EXT_CSD_REV 192 /* RO */ #define EXT_CSD_SEC_CNT 212 /* RO, 4 bytes */ /* @@ -253,5 +254,13 @@ #define MMC_SWITCH_MODE_SET_BITS 0x01 /* #define MMC_SWITCH_MODE_CLEAR_BITS 0x02 /* Clear bits which are 1 in value */ #define MMC_SWITCH_MODE_WRITE_BYTE 0x03 /* Set target to value */ +/* + * MMC_LOCK_UNLOCK modes + */ +#define MMC_LOCK_MODE_ERASE (1<<3) +#define MMC_LOCK_MODE_UNLOCK (1<<2) +#define MMC_LOCK_MODE_CLR_PWD (1<<1) +#define MMC_LOCK_MODE_SET_PWD (1<<0) + #endif /* MMC_MMC_PROTOCOL_H */ diff --git a/include/linux/mmc/sdio.h b/include/linux/mmc/sdio.h new file mode 100644 index 0000000..145ce23 --- /dev/null +++ b/include/linux/mmc/sdio.h @@ -0,0 +1,157 @@ +/* + * include/linux/mmc/sdio.h + * + * 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_H +#define MMC_SDIO_H + +/* SDIO commands type argument response */ +#define SD_IO_SEND_OP_COND 5 /* bcr [23:0] OCR R4 */ +#define SD_IO_RW_DIRECT 52 /* ac [31:0] See below R5 */ +#define SD_IO_RW_EXTENDED 53 /* adtc [31:0] See below R5 */ + +/* + * SD_IO_RW_DIRECT argument format: + * + * [31] R/W flag + * [30:28] Function number + * [27] RAW flag + * [25:9] Register address + * [7:0] Data + */ + +/* + * SD_IO_RW_EXTENDED argument format: + * + * [31] R/W flag + * [30:28] Function number + * [27] Block mode + * [26] Increment address + * [25:9] Register address + * [8:0] Byte/block count + */ + +/* + SDIO status in R5 + Type + e : error bit + s : status bit + r : detected and set for the actual command response + x : detected and set during command execution. the host must poll + the card by sending status command in order to read these bits. + Clear condition + a : according to the card state + b : always related to the previous command. Reception of + a valid command will clear it (with a delay of one command) + c : clear by read + */ + +#define R5_COM_CRC_ERROR (1 << 15) /* er, b */ +#define R5_ILLEGAL_COMMAND (1 << 14) /* er, b */ +#define R5_ERROR (1 << 11) /* erx, c */ +#define R5_FUNCTION_NUMBER (1 << 9) /* er, c */ +#define R5_OUT_OF_RANGE (1 << 8) /* er, c */ +#define R5_STATUS(x) (x & 0xCB00) +#define R5_IO_CURRENT_STATE(x) ((x & 0x3000) >> 12) /* s, b */ + +/* + * Card Common Control Registers (CCCR) + */ + +#define SDIO_CCCR_CCCR 0x00 + +#define SDIO_CCCR_REV_1_00 0 /* CCCR/FBR Version 1.00 */ +#define SDIO_CCCR_REV_1_10 1 /* CCCR/FBR Version 1.10 */ +#define SDIO_CCCR_REV_1_20 2 /* CCCR/FBR Version 1.20 */ + +#define SDIO_SDIO_REV_1_00 0 /* SDIO Spec Version 1.00 */ +#define SDIO_SDIO_REV_1_10 1 /* SDIO Spec Version 1.10 */ +#define SDIO_SDIO_REV_1_20 2 /* SDIO Spec Version 1.20 */ +#define SDIO_SDIO_REV_2_00 3 /* SDIO Spec Version 2.00 */ + +#define SDIO_CCCR_SD 0x01 + +#define SDIO_SD_REV_1_01 0 /* SD Physical Spec Version 1.01 */ +#define SDIO_SD_REV_1_10 1 /* SD Physical Spec Version 1.10 */ +#define SDIO_SD_REV_2_00 2 /* SD Physical Spec Version 2.00 */ + +#define SDIO_CCCR_IOEx 0x02 +#define SDIO_CCCR_IORx 0x03 + +#define SDIO_CCCR_IENx 0x04 /* Function/Master Interrupt Enable */ +#define SDIO_CCCR_INTx 0x05 /* Function Interrupt Pending */ + +#define SDIO_CCCR_ABORT 0x06 /* function abort/card reset */ + +#define SDIO_CCCR_IF 0x07 /* bus interface controls */ + +#define SDIO_BUS_WIDTH_1BIT 0x00 +#define SDIO_BUS_WIDTH_4BIT 0x02 + +#define SDIO_BUS_CD_DISABLE 0x80 /* disable pull-up on DAT3 (pin 1) */ + +#define SDIO_CCCR_CAPS 0x08 + +#define SDIO_CCCR_CAP_SDC 0x01 /* can do CMD52 while data transfer */ +#define SDIO_CCCR_CAP_SMB 0x02 /* can do multi-block xfers (CMD53) */ +#define SDIO_CCCR_CAP_SRW 0x04 /* supports read-wait protocol */ +#define SDIO_CCCR_CAP_SBS 0x08 /* supports suspend/resume */ +#define SDIO_CCCR_CAP_S4MI 0x10 /* interrupt during 4-bit CMD53 */ +#define SDIO_CCCR_CAP_E4MI 0x20 /* enable ints during 4-bit CMD53 */ +#define SDIO_CCCR_CAP_LSC 0x40 /* low speed card */ +#define SDIO_CCCR_CAP_4BLS 0x80 /* 4 bit low speed card */ + +#define SDIO_CCCR_CIS 0x09 /* common CIS pointer (3 bytes) */ + +/* Following 4 regs are valid only if SBS is set */ +#define SDIO_CCCR_SUSPEND 0x0c +#define SDIO_CCCR_SELx 0x0d +#define SDIO_CCCR_EXECx 0x0e +#define SDIO_CCCR_READYx 0x0f + +#define SDIO_CCCR_BLKSIZE 0x10 + +#define SDIO_CCCR_POWER 0x12 + +#define SDIO_POWER_SMPC 0x01 /* Supports Master Power Control */ +#define SDIO_POWER_EMPC 0x02 /* Enable Master Power Control */ + +#define SDIO_CCCR_SPEED 0x13 + +#define SDIO_SPEED_SHS 0x01 /* Supports High-Speed mode */ +#define SDIO_SPEED_EHS 0x02 /* Enable High-Speed mode */ + +/* + * Function Basic Registers (FBR) + */ + +#define SDIO_FBR_STD_IF 0x00 + +#define SDIO_FBR_SUPPORTS_CSA 0x40 /* supports Code Storage Area */ +#define SDIO_FBR_ENABLE_CSA 0x80 /* enable Code Storage Area */ + +#define SDIO_FBR_STD_IF_EXT 0x01 + +#define SDIO_FBR_POWER 0x02 + +#define SDIO_FBR_POWER_SPS 0x01 /* Supports Power Selection */ +#define SDIO_FBR_POWER_EPS 0x02 /* Enable (low) Power Selection */ + +#define SDIO_FBR_CIS 0x09 /* CIS pointer (3 bytes) */ + + +#define SDIO_FBR_CSA 0x0C /* CSA pointer (3 bytes) */ + +#define SDIO_FBR_CSA_DATA 0x0F + +#define SDIO_FBR_BLKSIZE 0x10 /* block size (2 bytes) */ + +#endif + diff --git a/include/linux/mmc/sdio_func.h b/include/linux/mmc/sdio_func.h new file mode 100644 index 0000000..e34bc96 --- /dev/null +++ b/include/linux/mmc/sdio_func.h @@ -0,0 +1,138 @@ +/* + * include/linux/mmc/sdio_func.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_SDIO_FUNC_H +#define MMC_SDIO_FUNC_H + +#include +#include + +struct mmc_card; +struct sdio_func; + +typedef void (sdio_irq_handler_t)(struct sdio_func *); + +/* + * SDIO function CIS tuple (unknown to the core) + */ +struct sdio_func_tuple { + struct sdio_func_tuple *next; + unsigned char code; + unsigned char size; + unsigned char data[0]; +}; + +/* + * SDIO function devices + */ +struct sdio_func { + struct mmc_card *card; /* the card this device belongs to */ + struct device dev; /* the device */ + sdio_irq_handler_t *irq_handler; /* IRQ callback */ + unsigned int num; /* function number */ + + unsigned char class; /* standard interface class */ + unsigned short vendor; /* vendor id */ + unsigned short device; /* device id */ + + unsigned int state; /* function state */ +#define SDIO_STATE_PRESENT (1<<0) /* present in sysfs */ + + struct sdio_func_tuple *tuples; +}; + +#define sdio_func_present(f) ((f)->state & SDIO_STATE_PRESENT) + +#define sdio_func_set_present(f) ((f)->state |= SDIO_STATE_PRESENT) + +#define sdio_func_id(f) ((f)->dev.bus_id) + +#define sdio_get_drvdata(f) dev_get_drvdata(&(f)->dev) +#define sdio_set_drvdata(f,d) dev_set_drvdata(&(f)->dev, d) + +/* + * SDIO function device driver + */ +struct sdio_driver { + char *name; + const struct sdio_device_id *id_table; + + int (*probe)(struct sdio_func *, const struct sdio_device_id *); + void (*remove)(struct sdio_func *); + + struct device_driver drv; +}; + +/** + * SDIO_DEVICE - macro used to describe a specific SDIO device + * @vend: the 16 bit manufacturer code + * @dev: the 16 bit function id + * + * This macro is used to create a struct sdio_device_id that matches a + * specific device. The class field will be set to SDIO_ANY_ID. + */ +#define SDIO_DEVICE(vend,dev) \ + .class = SDIO_ANY_ID, \ + .vendor = (vend), .device = (dev) + +/** + * SDIO_DEVICE_CLASS - macro used to describe a specific SDIO device class + * @dev_class: the 8 bit standard interface code + * + * This macro is used to create a struct sdio_device_id that matches a + * specific standard SDIO function type. The vendor and device fields will + * be set to SDIO_ANY_ID. + */ +#define SDIO_DEVICE_CLASS(dev_class) \ + .class = (dev_class), \ + .vendor = SDIO_ANY_ID, .device = SDIO_ANY_ID + +extern int sdio_register_driver(struct sdio_driver *); +extern void sdio_unregister_driver(struct sdio_driver *); + +/* + * SDIO I/O operations + */ +extern void sdio_claim_host(struct sdio_func *func); +extern void sdio_release_host(struct sdio_func *func); + +extern int sdio_enable_func(struct sdio_func *func); +extern int sdio_disable_func(struct sdio_func *func); + +extern int sdio_claim_irq(struct sdio_func *func, sdio_irq_handler_t *handler); +extern int sdio_release_irq(struct sdio_func *func); + +extern unsigned char sdio_readb(struct sdio_func *func, + unsigned int addr, int *err_ret); +extern unsigned short sdio_readw(struct sdio_func *func, + unsigned int addr, int *err_ret); +extern unsigned long sdio_readl(struct sdio_func *func, + unsigned int addr, int *err_ret); + +extern int sdio_memcpy_fromio(struct sdio_func *func, void *dst, + unsigned int addr, int count); +extern int sdio_readsb(struct sdio_func *func, void *dst, + unsigned int addr, int count); + +extern void sdio_writeb(struct sdio_func *func, unsigned char b, + unsigned int addr, int *err_ret); +extern void sdio_writew(struct sdio_func *func, unsigned short b, + unsigned int addr, int *err_ret); +extern void sdio_writel(struct sdio_func *func, unsigned long b, + unsigned int addr, int *err_ret); + +extern int sdio_memcpy_toio(struct sdio_func *func, unsigned int addr, + void *src, int count); +extern int sdio_writesb(struct sdio_func *func, unsigned int addr, + void *src, int count); + +#endif + diff --git a/include/linux/mmc/sdio_ids.h b/include/linux/mmc/sdio_ids.h new file mode 100644 index 0000000..09306d4 --- /dev/null +++ b/include/linux/mmc/sdio_ids.h @@ -0,0 +1,23 @@ +/* + * SDIO Classes, Interface Types, Manufacturer IDs, etc. + */ + +#ifndef MMC_SDIO_IDS_H +#define MMC_SDIO_IDS_H + +/* + * Standard SDIO Function Interfaces + */ + +#define SDIO_CLASS_NONE 0x00 /* Not a SDIO standard interface */ +#define SDIO_CLASS_UART 0x01 /* standard UART interface */ +#define SDIO_CLASS_BT_A 0x02 /* Type-A BlueTooth std interface */ +#define SDIO_CLASS_BT_B 0x03 /* Type-B BlueTooth std interface */ +#define SDIO_CLASS_GPS 0x04 /* GPS standard interface */ +#define SDIO_CLASS_CAMERA 0x05 /* Camera standard interface */ +#define SDIO_CLASS_PHS 0x06 /* PHS standard interface */ +#define SDIO_CLASS_WLAN 0x07 /* WLAN interface */ +#define SDIO_CLASS_ATA 0x08 /* Embedded SDIO-ATA std interface */ + + +#endif diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index 2ada8ee..655b319 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -339,4 +339,15 @@ #define PA_HVERSION_REV_ANY_ID 0xff #define PA_HVERSION_ANY_ID 0xffff #define PA_SVERSION_ANY_ID 0xffffffff +/* SDIO */ + +#define SDIO_ANY_ID (~0) + +struct sdio_device_id { + __u8 class; /* Standard interface or SDIO_ANY_ID */ + __u16 vendor; /* Vendor or SDIO_ANY_ID */ + __u16 device; /* Device ID or SDIO_ANY_ID */ + kernel_ulong_t driver_data; /* Data private to the driver */ +}; + #endif /* LINUX_MOD_DEVICETABLE_H */ diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c index 8a09021..1e5d4d6 100644 --- a/scripts/mod/file2alias.c +++ b/scripts/mod/file2alias.c @@ -484,6 +484,22 @@ static int do_parisc_entry(const char *f return 1; } +/* Looks like: sdio:cNvNdN. */ +static int do_sdio_entry(const char *filename, + struct sdio_device_id *id, char *alias) +{ + id->class = TO_NATIVE(id->class); + id->vendor = TO_NATIVE(id->vendor); + id->device = TO_NATIVE(id->device); + + strcpy(alias, "sdio:"); + ADD(alias, "c", id->class != (__u8)SDIO_ANY_ID, id->class); + ADD(alias, "v", id->vendor != (__u16)SDIO_ANY_ID, id->vendor); + ADD(alias, "d", id->device != (__u16)SDIO_ANY_ID, id->device); + + return 1; +} + /* Ignore any prefix, eg. v850 prepends _ */ static inline int sym_is(const char *symbol, const char *name) { @@ -599,6 +615,10 @@ void handle_moddevtable(struct module *m do_table(symval, sym->st_size, sizeof(struct parisc_device_id), "parisc", do_parisc_entry, mod); + else if (sym_is(symname, "__mod_sdio_device_table")) + do_table(symval, sym->st_size, + sizeof(struct sdio_device_id), "sdio", + do_sdio_entry, mod); } /* Now add out buffered information to the generated C source */