mmc,sdio: helper function for transfer padding
authorPierre Ossman <drzeus@drzeus.cx>
Sat, 28 Jun 2008 10:52:45 +0000 (12:52 +0200)
committerPierre Ossman <drzeus@drzeus.cx>
Tue, 15 Jul 2008 12:14:44 +0000 (14:14 +0200)
There are a lot of crappy controllers out there that cannot handle
all the request sizes that the MMC/SD/SDIO specifications require.
In case the card driver can pad the data to overcome the problems,
this commit adds a helper that calculates how much that padding
should be.

A corresponding helper is also added for SDIO, but it can also deal
with all the complexities of splitting up a large transfer efficiently.

Signed-off-by: Pierre Ossman <drzeus@drzeus.cx>
drivers/mmc/core/core.c
drivers/mmc/core/sdio_io.c
drivers/net/wireless/libertas/if_sdio.c
include/linux/mmc/core.h
include/linux/mmc/sdio_func.h

index ede5d1e2e20dfe4626ef3814dae5e886c8374372..3ee5b8c3b5ce42fa205d3bbc2ee2d5e4f6420794 100644 (file)
@@ -3,7 +3,7 @@
  *
  *  Copyright (C) 2003-2004 Russell King, All Rights Reserved.
  *  SD support Copyright (C) 2004 Ian Molton, All Rights Reserved.
- *  Copyright (C) 2005-2007 Pierre Ossman, All Rights Reserved.
+ *  Copyright (C) 2005-2008 Pierre Ossman, All Rights Reserved.
  *  MMCv4 support Copyright (C) 2006 Philip Langdale, All Rights Reserved.
  *
  * This program is free software; you can redistribute it and/or modify
@@ -294,6 +294,33 @@ void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card)
 }
 EXPORT_SYMBOL(mmc_set_data_timeout);
 
+/**
+ *     mmc_align_data_size - pads a transfer size to a more optimal value
+ *     @card: the MMC card associated with the data transfer
+ *     @sz: original transfer size
+ *
+ *     Pads the original data size with a number of extra bytes in
+ *     order to avoid controller bugs and/or performance hits
+ *     (e.g. some controllers revert to PIO for certain sizes).
+ *
+ *     Returns the improved size, which might be unmodified.
+ *
+ *     Note that this function is only relevant when issuing a
+ *     single scatter gather entry.
+ */
+unsigned int mmc_align_data_size(struct mmc_card *card, unsigned int sz)
+{
+       /*
+        * FIXME: We don't have a system for the controller to tell
+        * the core about its problems yet, so for now we just 32-bit
+        * align the size.
+        */
+       sz = ((sz + 3) / 4) * 4;
+
+       return sz;
+}
+EXPORT_SYMBOL(mmc_align_data_size);
+
 /**
  *     __mmc_claim_host - exclusively claim a host
  *     @host: mmc host to claim
index 625b92ce9cef2abedbaf853f2e0d2ba6dc7a7314..6ee7861fceae96b49ac8ffbbeda28c4bc3d65c45 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  linux/drivers/mmc/core/sdio_io.c
  *
- *  Copyright 2007 Pierre Ossman
+ *  Copyright 2007-2008 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
@@ -189,6 +189,103 @@ int sdio_set_block_size(struct sdio_func *func, unsigned blksz)
 
 EXPORT_SYMBOL_GPL(sdio_set_block_size);
 
+/**
+ *     sdio_align_size - pads a transfer size to a more optimal value
+ *     @func: SDIO function
+ *     @sz: original transfer size
+ *
+ *     Pads the original data size with a number of extra bytes in
+ *     order to avoid controller bugs and/or performance hits
+ *     (e.g. some controllers revert to PIO for certain sizes).
+ *
+ *     If possible, it will also adjust the size so that it can be
+ *     handled in just a single request.
+ *
+ *     Returns the improved size, which might be unmodified.
+ */
+unsigned int sdio_align_size(struct sdio_func *func, unsigned int sz)
+{
+       unsigned int orig_sz;
+       unsigned int blk_sz, byte_sz;
+       unsigned chunk_sz;
+
+       orig_sz = sz;
+
+       /*
+        * Do a first check with the controller, in case it
+        * wants to increase the size up to a point where it
+        * might need more than one block.
+        */
+       sz = mmc_align_data_size(func->card, sz);
+
+       /*
+        * If we can still do this with just a byte transfer, then
+        * we're done.
+        */
+       if ((sz <= func->cur_blksize) && (sz <= 512))
+               return sz;
+
+       if (func->card->cccr.multi_block) {
+               /*
+                * Check if the transfer is already block aligned
+                */
+               if ((sz % func->cur_blksize) == 0)
+                       return sz;
+
+               /*
+                * Realign it so that it can be done with one request,
+                * and recheck if the controller still likes it.
+                */
+               blk_sz = ((sz + func->cur_blksize - 1) /
+                       func->cur_blksize) * func->cur_blksize;
+               blk_sz = mmc_align_data_size(func->card, blk_sz);
+
+               /*
+                * This value is only good if it is still just
+                * one request.
+                */
+               if ((blk_sz % func->cur_blksize) == 0)
+                       return blk_sz;
+
+               /*
+                * We failed to do one request, but at least try to
+                * pad the remainder properly.
+                */
+               byte_sz = mmc_align_data_size(func->card,
+                               sz % func->cur_blksize);
+               if ((byte_sz <= func->cur_blksize) && (byte_sz <= 512)) {
+                       blk_sz = sz / func->cur_blksize;
+                       return blk_sz * func->cur_blksize + byte_sz;
+               }
+       } else {
+               /*
+                * We need multiple requests, so first check that the
+                * controller can handle the chunk size;
+                */
+               chunk_sz = mmc_align_data_size(func->card,
+                               min(func->cur_blksize, 512u));
+               if (chunk_sz == min(func->cur_blksize, 512u)) {
+                       /*
+                        * Fix up the size of the remainder (if any)
+                        */
+                       byte_sz = orig_sz % chunk_sz;
+                       if (byte_sz) {
+                               byte_sz = mmc_align_data_size(func->card,
+                                               byte_sz);
+                       }
+
+                       return (orig_sz / chunk_sz) * chunk_sz + byte_sz;
+               }
+       }
+
+       /*
+        * The controller is simply incapable of transferring the size
+        * we want in decent manner, so just return the original size.
+        */
+       return orig_sz;
+}
+EXPORT_SYMBOL_GPL(sdio_align_size);
+
 /* Split an arbitrarily sized data transfer into several
  * IO_RW_EXTENDED commands. */
 static int sdio_io_rw_ext_helper(struct sdio_func *func, int write,
index 3dd537be87d803bdb74ec7653bcd22c3a396d505..b54e2ea8346bf26ae5071ed39bbfe450830a980d 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  linux/drivers/net/wireless/libertas/if_sdio.c
  *
- *  Copyright 2007 Pierre Ossman
+ *  Copyright 2007-2008 Pierre Ossman
  *
  * Inspired by if_cs.c, Copyright 2007 Holger Schurig
  *
@@ -266,13 +266,10 @@ static int if_sdio_card_to_host(struct if_sdio_card *card)
 
        /*
         * The transfer must be in one transaction or the firmware
-        * goes suicidal.
+        * goes suicidal. There's no way to guarantee that for all
+        * controllers, but we can at least try.
         */
-       chunk = size;
-       if ((chunk > card->func->cur_blksize) || (chunk > 512)) {
-               chunk = (chunk + card->func->cur_blksize - 1) /
-                       card->func->cur_blksize * card->func->cur_blksize;
-       }
+       chunk = sdio_align_size(card->func, size);
 
        ret = sdio_readsb(card->func, card->buffer, card->ioport, chunk);
        if (ret)
@@ -696,13 +693,10 @@ static int if_sdio_host_to_card(struct lbs_private *priv,
 
        /*
         * The transfer must be in one transaction or the firmware
-        * goes suicidal.
+        * goes suicidal. There's no way to guarantee that for all
+        * controllers, but we can at least try.
         */
-       size = nb + 4;
-       if ((size > card->func->cur_blksize) || (size > 512)) {
-               size = (size + card->func->cur_blksize - 1) /
-                       card->func->cur_blksize * card->func->cur_blksize;
-       }
+       size = sdio_align_size(card->func, nb + 4);
 
        packet = kzalloc(sizeof(struct if_sdio_packet) + size,
                        GFP_ATOMIC);
index d0c3abed74c2cbb5cbc71c6bd563bd04dad03fdc..143cebf0586f140fd267bf360d69654281584d11 100644 (file)
@@ -135,6 +135,7 @@ 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 *);
+extern unsigned int mmc_align_data_size(struct mmc_card *, unsigned int);
 
 extern int __mmc_claim_host(struct mmc_host *host, atomic_t *abort);
 extern void mmc_release_host(struct mmc_host *host);
index b050f4d7b41f05af47bf98c342c0b4528d110e19..f57f22b3be889178ff48a1e0082ad1f7ecaf3088 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  include/linux/mmc/sdio_func.h
  *
- *  Copyright 2007 Pierre Ossman
+ *  Copyright 2007-2008 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
@@ -120,6 +120,8 @@ extern int sdio_set_block_size(struct sdio_func *func, unsigned blksz);
 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 int sdio_align_size(struct sdio_func *func, unsigned int sz);
+
 extern unsigned char sdio_readb(struct sdio_func *func,
        unsigned int addr, int *err_ret);
 extern unsigned short sdio_readw(struct sdio_func *func,