sdio: support IO_RW_EXTENDED

Support the multi-byte transfer operation, including handlers for
common operations like writel()/readl().

Signed-off-by: Pierre Ossman <drzeus@drzeus.cx>
diff --git a/drivers/mmc/core/sdio_io.c b/drivers/mmc/core/sdio_io.c
index eb6c209..ecdb772 100644
--- a/drivers/mmc/core/sdio_io.c
+++ b/drivers/mmc/core/sdio_io.c
@@ -196,3 +196,187 @@
 }
 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);
+}
+EXPORT_SYMBOL_GPL(sdio_memcpy_toio);
+
+/**
+ *	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;
+
+	if (err_ret)
+		*err_ret = 0;
+
+	ret = sdio_memcpy_fromio(func, func->tmpbuf, addr, 2);
+	if (ret) {
+		if (err_ret)
+			*err_ret = ret;
+		return 0xFFFF;
+	}
+
+	return le16_to_cpu(*(u16*)func->tmpbuf);
+}
+EXPORT_SYMBOL_GPL(sdio_readw);
+
+/**
+ *	sdio_writew - write a 16 bit integer to a SDIO function
+ *	@func: SDIO function to access
+ *	@b: integer to write
+ *	@addr: address to write to
+ *	@err_ret: optional status value from transfer
+ *
+ *	Writes a 16 bit integer to the address space of a given SDIO
+ *	function. @err_ret will contain the status of the actual
+ *	transfer.
+ */
+void sdio_writew(struct sdio_func *func, unsigned short b, unsigned int addr,
+	int *err_ret)
+{
+	int ret;
+
+	*(u16*)func->tmpbuf = cpu_to_le16(b);
+
+	ret = sdio_memcpy_toio(func, addr, func->tmpbuf, 2);
+	if (err_ret)
+		*err_ret = ret;
+}
+EXPORT_SYMBOL_GPL(sdio_writew);
+
+/**
+ *	sdio_readl - read a 32 bit integer from a SDIO function
+ *	@func: SDIO function to access
+ *	@addr: address to read
+ *	@err_ret: optional status value from transfer
+ *
+ *	Reads a 32 bit integer from the address space of a given SDIO
+ *	function. If there is a problem reading the address,
+ *	0xffffffff is returned and @err_ret will contain the error
+ *	code.
+ */
+unsigned long sdio_readl(struct sdio_func *func, unsigned int addr,
+	int *err_ret)
+{
+	int ret;
+
+	if (err_ret)
+		*err_ret = 0;
+
+	ret = sdio_memcpy_fromio(func, func->tmpbuf, addr, 4);
+	if (ret) {
+		if (err_ret)
+			*err_ret = ret;
+		return 0xFFFFFFFF;
+	}
+
+	return le32_to_cpu(*(u32*)func->tmpbuf);
+}
+EXPORT_SYMBOL_GPL(sdio_readl);
+
+/**
+ *	sdio_writel - write a 32 bit integer to a SDIO function
+ *	@func: SDIO function to access
+ *	@b: integer to write
+ *	@addr: address to write to
+ *	@err_ret: optional status value from transfer
+ *
+ *	Writes a 32 bit integer to the address space of a given SDIO
+ *	function. @err_ret will contain the status of the actual
+ *	transfer.
+ */
+void sdio_writel(struct sdio_func *func, unsigned long b, unsigned int addr,
+	int *err_ret)
+{
+	int ret;
+
+	*(u32*)func->tmpbuf = cpu_to_le32(b);
+
+	ret = sdio_memcpy_toio(func, addr, func->tmpbuf, 4);
+	if (err_ret)
+		*err_ret = ret;
+}
+EXPORT_SYMBOL_GPL(sdio_writel);
+
diff --git a/drivers/mmc/core/sdio_ops.c b/drivers/mmc/core/sdio_ops.c
index 31233f7..4f2c771 100644
--- a/drivers/mmc/core/sdio_ops.c
+++ b/drivers/mmc/core/sdio_ops.c
@@ -9,6 +9,9 @@
  * your option) any later version.
  */
 
+#include <asm/scatterlist.h>
+#include <linux/scatterlist.h>
+
 #include <linux/mmc/host.h>
 #include <linux/mmc/card.h>
 #include <linux/mmc/mmc.h>
@@ -84,3 +87,57 @@
 	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
index f0e9d69..1d42e4f3 100644
--- a/drivers/mmc/core/sdio_ops.h
+++ b/drivers/mmc/core/sdio_ops.h
@@ -15,6 +15,8 @@
 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