diff options
author | Pierre Ossman <drzeus@drzeus.cx> | 2008-06-28 06:52:45 -0400 |
---|---|---|
committer | Pierre Ossman <drzeus@drzeus.cx> | 2008-07-15 08:14:44 -0400 |
commit | ad3868b2ec96ec14a1549c9e33f5f9a2a3c6ab15 (patch) | |
tree | 05ca55c5ab38b814bf8d71c0720e5dfaf1419e32 | |
parent | e2d2647702702ea08cb78cdc9eca8c24242aa9be (diff) |
mmc,sdio: helper function for transfer padding
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>
-rw-r--r-- | drivers/mmc/core/core.c | 29 | ||||
-rw-r--r-- | drivers/mmc/core/sdio_io.c | 99 | ||||
-rw-r--r-- | drivers/net/wireless/libertas/if_sdio.c | 20 | ||||
-rw-r--r-- | include/linux/mmc/core.h | 1 | ||||
-rw-r--r-- | include/linux/mmc/sdio_func.h | 4 |
5 files changed, 137 insertions, 16 deletions
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c index ede5d1e2e20d..3ee5b8c3b5ce 100644 --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c | |||
@@ -3,7 +3,7 @@ | |||
3 | * | 3 | * |
4 | * Copyright (C) 2003-2004 Russell King, All Rights Reserved. | 4 | * Copyright (C) 2003-2004 Russell King, All Rights Reserved. |
5 | * SD support Copyright (C) 2004 Ian Molton, All Rights Reserved. | 5 | * SD support Copyright (C) 2004 Ian Molton, All Rights Reserved. |
6 | * Copyright (C) 2005-2007 Pierre Ossman, All Rights Reserved. | 6 | * Copyright (C) 2005-2008 Pierre Ossman, All Rights Reserved. |
7 | * MMCv4 support Copyright (C) 2006 Philip Langdale, All Rights Reserved. | 7 | * MMCv4 support Copyright (C) 2006 Philip Langdale, All Rights Reserved. |
8 | * | 8 | * |
9 | * This program is free software; you can redistribute it and/or modify | 9 | * This program is free software; you can redistribute it and/or modify |
@@ -295,6 +295,33 @@ void mmc_set_data_timeout(struct mmc_data *data, const struct mmc_card *card) | |||
295 | EXPORT_SYMBOL(mmc_set_data_timeout); | 295 | EXPORT_SYMBOL(mmc_set_data_timeout); |
296 | 296 | ||
297 | /** | 297 | /** |
298 | * mmc_align_data_size - pads a transfer size to a more optimal value | ||
299 | * @card: the MMC card associated with the data transfer | ||
300 | * @sz: original transfer size | ||
301 | * | ||
302 | * Pads the original data size with a number of extra bytes in | ||
303 | * order to avoid controller bugs and/or performance hits | ||
304 | * (e.g. some controllers revert to PIO for certain sizes). | ||
305 | * | ||
306 | * Returns the improved size, which might be unmodified. | ||
307 | * | ||
308 | * Note that this function is only relevant when issuing a | ||
309 | * single scatter gather entry. | ||
310 | */ | ||
311 | unsigned int mmc_align_data_size(struct mmc_card *card, unsigned int sz) | ||
312 | { | ||
313 | /* | ||
314 | * FIXME: We don't have a system for the controller to tell | ||
315 | * the core about its problems yet, so for now we just 32-bit | ||
316 | * align the size. | ||
317 | */ | ||
318 | sz = ((sz + 3) / 4) * 4; | ||
319 | |||
320 | return sz; | ||
321 | } | ||
322 | EXPORT_SYMBOL(mmc_align_data_size); | ||
323 | |||
324 | /** | ||
298 | * __mmc_claim_host - exclusively claim a host | 325 | * __mmc_claim_host - exclusively claim a host |
299 | * @host: mmc host to claim | 326 | * @host: mmc host to claim |
300 | * @abort: whether or not the operation should be aborted | 327 | * @abort: whether or not the operation should be aborted |
diff --git a/drivers/mmc/core/sdio_io.c b/drivers/mmc/core/sdio_io.c index 625b92ce9cef..6ee7861fceae 100644 --- a/drivers/mmc/core/sdio_io.c +++ b/drivers/mmc/core/sdio_io.c | |||
@@ -1,7 +1,7 @@ | |||
1 | /* | 1 | /* |
2 | * linux/drivers/mmc/core/sdio_io.c | 2 | * linux/drivers/mmc/core/sdio_io.c |
3 | * | 3 | * |
4 | * Copyright 2007 Pierre Ossman | 4 | * Copyright 2007-2008 Pierre Ossman |
5 | * | 5 | * |
6 | * This program is free software; you can redistribute it and/or modify | 6 | * This program is free software; you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License as published by | 7 | * 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) | |||
189 | 189 | ||
190 | EXPORT_SYMBOL_GPL(sdio_set_block_size); | 190 | EXPORT_SYMBOL_GPL(sdio_set_block_size); |
191 | 191 | ||
192 | /** | ||
193 | * sdio_align_size - pads a transfer size to a more optimal value | ||
194 | * @func: SDIO function | ||
195 | * @sz: original transfer size | ||
196 | * | ||
197 | * Pads the original data size with a number of extra bytes in | ||
198 | * order to avoid controller bugs and/or performance hits | ||
199 | * (e.g. some controllers revert to PIO for certain sizes). | ||
200 | * | ||
201 | * If possible, it will also adjust the size so that it can be | ||
202 | * handled in just a single request. | ||
203 | * | ||
204 | * Returns the improved size, which might be unmodified. | ||
205 | */ | ||
206 | unsigned int sdio_align_size(struct sdio_func *func, unsigned int sz) | ||
207 | { | ||
208 | unsigned int orig_sz; | ||
209 | unsigned int blk_sz, byte_sz; | ||
210 | unsigned chunk_sz; | ||
211 | |||
212 | orig_sz = sz; | ||
213 | |||
214 | /* | ||
215 | * Do a first check with the controller, in case it | ||
216 | * wants to increase the size up to a point where it | ||
217 | * might need more than one block. | ||
218 | */ | ||
219 | sz = mmc_align_data_size(func->card, sz); | ||
220 | |||
221 | /* | ||
222 | * If we can still do this with just a byte transfer, then | ||
223 | * we're done. | ||
224 | */ | ||
225 | if ((sz <= func->cur_blksize) && (sz <= 512)) | ||
226 | return sz; | ||
227 | |||
228 | if (func->card->cccr.multi_block) { | ||
229 | /* | ||
230 | * Check if the transfer is already block aligned | ||
231 | */ | ||
232 | if ((sz % func->cur_blksize) == 0) | ||
233 | return sz; | ||
234 | |||
235 | /* | ||
236 | * Realign it so that it can be done with one request, | ||
237 | * and recheck if the controller still likes it. | ||
238 | */ | ||
239 | blk_sz = ((sz + func->cur_blksize - 1) / | ||
240 | func->cur_blksize) * func->cur_blksize; | ||
241 | blk_sz = mmc_align_data_size(func->card, blk_sz); | ||
242 | |||
243 | /* | ||
244 | * This value is only good if it is still just | ||
245 | * one request. | ||
246 | */ | ||
247 | if ((blk_sz % func->cur_blksize) == 0) | ||
248 | return blk_sz; | ||
249 | |||
250 | /* | ||
251 | * We failed to do one request, but at least try to | ||
252 | * pad the remainder properly. | ||
253 | */ | ||
254 | byte_sz = mmc_align_data_size(func->card, | ||
255 | sz % func->cur_blksize); | ||
256 | if ((byte_sz <= func->cur_blksize) && (byte_sz <= 512)) { | ||
257 | blk_sz = sz / func->cur_blksize; | ||
258 | return blk_sz * func->cur_blksize + byte_sz; | ||
259 | } | ||
260 | } else { | ||
261 | /* | ||
262 | * We need multiple requests, so first check that the | ||
263 | * controller can handle the chunk size; | ||
264 | */ | ||
265 | chunk_sz = mmc_align_data_size(func->card, | ||
266 | min(func->cur_blksize, 512u)); | ||
267 | if (chunk_sz == min(func->cur_blksize, 512u)) { | ||
268 | /* | ||
269 | * Fix up the size of the remainder (if any) | ||
270 | */ | ||
271 | byte_sz = orig_sz % chunk_sz; | ||
272 | if (byte_sz) { | ||
273 | byte_sz = mmc_align_data_size(func->card, | ||
274 | byte_sz); | ||
275 | } | ||
276 | |||
277 | return (orig_sz / chunk_sz) * chunk_sz + byte_sz; | ||
278 | } | ||
279 | } | ||
280 | |||
281 | /* | ||
282 | * The controller is simply incapable of transferring the size | ||
283 | * we want in decent manner, so just return the original size. | ||
284 | */ | ||
285 | return orig_sz; | ||
286 | } | ||
287 | EXPORT_SYMBOL_GPL(sdio_align_size); | ||
288 | |||
192 | /* Split an arbitrarily sized data transfer into several | 289 | /* Split an arbitrarily sized data transfer into several |
193 | * IO_RW_EXTENDED commands. */ | 290 | * IO_RW_EXTENDED commands. */ |
194 | static int sdio_io_rw_ext_helper(struct sdio_func *func, int write, | 291 | static int sdio_io_rw_ext_helper(struct sdio_func *func, int write, |
diff --git a/drivers/net/wireless/libertas/if_sdio.c b/drivers/net/wireless/libertas/if_sdio.c index 3dd537be87d8..b54e2ea8346b 100644 --- a/drivers/net/wireless/libertas/if_sdio.c +++ b/drivers/net/wireless/libertas/if_sdio.c | |||
@@ -1,7 +1,7 @@ | |||
1 | /* | 1 | /* |
2 | * linux/drivers/net/wireless/libertas/if_sdio.c | 2 | * linux/drivers/net/wireless/libertas/if_sdio.c |
3 | * | 3 | * |
4 | * Copyright 2007 Pierre Ossman | 4 | * Copyright 2007-2008 Pierre Ossman |
5 | * | 5 | * |
6 | * Inspired by if_cs.c, Copyright 2007 Holger Schurig | 6 | * Inspired by if_cs.c, Copyright 2007 Holger Schurig |
7 | * | 7 | * |
@@ -266,13 +266,10 @@ static int if_sdio_card_to_host(struct if_sdio_card *card) | |||
266 | 266 | ||
267 | /* | 267 | /* |
268 | * The transfer must be in one transaction or the firmware | 268 | * The transfer must be in one transaction or the firmware |
269 | * goes suicidal. | 269 | * goes suicidal. There's no way to guarantee that for all |
270 | * controllers, but we can at least try. | ||
270 | */ | 271 | */ |
271 | chunk = size; | 272 | chunk = sdio_align_size(card->func, size); |
272 | if ((chunk > card->func->cur_blksize) || (chunk > 512)) { | ||
273 | chunk = (chunk + card->func->cur_blksize - 1) / | ||
274 | card->func->cur_blksize * card->func->cur_blksize; | ||
275 | } | ||
276 | 273 | ||
277 | ret = sdio_readsb(card->func, card->buffer, card->ioport, chunk); | 274 | ret = sdio_readsb(card->func, card->buffer, card->ioport, chunk); |
278 | if (ret) | 275 | if (ret) |
@@ -696,13 +693,10 @@ static int if_sdio_host_to_card(struct lbs_private *priv, | |||
696 | 693 | ||
697 | /* | 694 | /* |
698 | * The transfer must be in one transaction or the firmware | 695 | * The transfer must be in one transaction or the firmware |
699 | * goes suicidal. | 696 | * goes suicidal. There's no way to guarantee that for all |
697 | * controllers, but we can at least try. | ||
700 | */ | 698 | */ |
701 | size = nb + 4; | 699 | size = sdio_align_size(card->func, nb + 4); |
702 | if ((size > card->func->cur_blksize) || (size > 512)) { | ||
703 | size = (size + card->func->cur_blksize - 1) / | ||
704 | card->func->cur_blksize * card->func->cur_blksize; | ||
705 | } | ||
706 | 700 | ||
707 | packet = kzalloc(sizeof(struct if_sdio_packet) + size, | 701 | packet = kzalloc(sizeof(struct if_sdio_packet) + size, |
708 | GFP_ATOMIC); | 702 | GFP_ATOMIC); |
diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index d0c3abed74c2..143cebf0586f 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h | |||
@@ -135,6 +135,7 @@ extern int mmc_wait_for_app_cmd(struct mmc_host *, struct mmc_card *, | |||
135 | struct mmc_command *, int); | 135 | struct mmc_command *, int); |
136 | 136 | ||
137 | extern void mmc_set_data_timeout(struct mmc_data *, const struct mmc_card *); | 137 | extern void mmc_set_data_timeout(struct mmc_data *, const struct mmc_card *); |
138 | extern unsigned int mmc_align_data_size(struct mmc_card *, unsigned int); | ||
138 | 139 | ||
139 | extern int __mmc_claim_host(struct mmc_host *host, atomic_t *abort); | 140 | extern int __mmc_claim_host(struct mmc_host *host, atomic_t *abort); |
140 | extern void mmc_release_host(struct mmc_host *host); | 141 | extern void mmc_release_host(struct mmc_host *host); |
diff --git a/include/linux/mmc/sdio_func.h b/include/linux/mmc/sdio_func.h index b050f4d7b41f..f57f22b3be88 100644 --- a/include/linux/mmc/sdio_func.h +++ b/include/linux/mmc/sdio_func.h | |||
@@ -1,7 +1,7 @@ | |||
1 | /* | 1 | /* |
2 | * include/linux/mmc/sdio_func.h | 2 | * include/linux/mmc/sdio_func.h |
3 | * | 3 | * |
4 | * Copyright 2007 Pierre Ossman | 4 | * Copyright 2007-2008 Pierre Ossman |
5 | * | 5 | * |
6 | * This program is free software; you can redistribute it and/or modify | 6 | * This program is free software; you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License as published by | 7 | * 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); | |||
120 | extern int sdio_claim_irq(struct sdio_func *func, sdio_irq_handler_t *handler); | 120 | extern int sdio_claim_irq(struct sdio_func *func, sdio_irq_handler_t *handler); |
121 | extern int sdio_release_irq(struct sdio_func *func); | 121 | extern int sdio_release_irq(struct sdio_func *func); |
122 | 122 | ||
123 | extern unsigned int sdio_align_size(struct sdio_func *func, unsigned int sz); | ||
124 | |||
123 | extern unsigned char sdio_readb(struct sdio_func *func, | 125 | extern unsigned char sdio_readb(struct sdio_func *func, |
124 | unsigned int addr, int *err_ret); | 126 | unsigned int addr, int *err_ret); |
125 | extern unsigned short sdio_readw(struct sdio_func *func, | 127 | extern unsigned short sdio_readw(struct sdio_func *func, |