diff options
Diffstat (limited to 'drivers/mmc/host/s3cmci.c')
-rw-r--r-- | drivers/mmc/host/s3cmci.c | 210 |
1 files changed, 159 insertions, 51 deletions
diff --git a/drivers/mmc/host/s3cmci.c b/drivers/mmc/host/s3cmci.c index ae16d845d746..3b2085b57769 100644 --- a/drivers/mmc/host/s3cmci.c +++ b/drivers/mmc/host/s3cmci.c | |||
@@ -3,6 +3,9 @@ | |||
3 | * | 3 | * |
4 | * Copyright (C) 2004-2006 maintech GmbH, Thomas Kleffel <tk@maintech.de> | 4 | * Copyright (C) 2004-2006 maintech GmbH, Thomas Kleffel <tk@maintech.de> |
5 | * | 5 | * |
6 | * Current driver maintained by Ben Dooks and Simtec Electronics | ||
7 | * Copyright (C) 2008 Simtec Electronics <ben-linux@fluff.org> | ||
8 | * | ||
6 | * 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 |
7 | * it under the terms of the GNU General Public License version 2 as | 10 | * it under the terms of the GNU General Public License version 2 as |
8 | * published by the Free Software Foundation. | 11 | * published by the Free Software Foundation. |
@@ -13,6 +16,7 @@ | |||
13 | #include <linux/clk.h> | 16 | #include <linux/clk.h> |
14 | #include <linux/mmc/host.h> | 17 | #include <linux/mmc/host.h> |
15 | #include <linux/platform_device.h> | 18 | #include <linux/platform_device.h> |
19 | #include <linux/cpufreq.h> | ||
16 | #include <linux/irq.h> | 20 | #include <linux/irq.h> |
17 | #include <linux/io.h> | 21 | #include <linux/io.h> |
18 | 22 | ||
@@ -39,9 +43,9 @@ enum dbg_channels { | |||
39 | dbg_conf = (1 << 8), | 43 | dbg_conf = (1 << 8), |
40 | }; | 44 | }; |
41 | 45 | ||
42 | static const int dbgmap_err = dbg_err | dbg_fail; | 46 | static const int dbgmap_err = dbg_fail; |
43 | static const int dbgmap_info = dbg_info | dbg_conf; | 47 | static const int dbgmap_info = dbg_info | dbg_conf; |
44 | static const int dbgmap_debug = dbg_debug; | 48 | static const int dbgmap_debug = dbg_err | dbg_debug; |
45 | 49 | ||
46 | #define dbg(host, channels, args...) \ | 50 | #define dbg(host, channels, args...) \ |
47 | do { \ | 51 | do { \ |
@@ -189,7 +193,7 @@ static inline void clear_imask(struct s3cmci_host *host) | |||
189 | } | 193 | } |
190 | 194 | ||
191 | static inline int get_data_buffer(struct s3cmci_host *host, | 195 | static inline int get_data_buffer(struct s3cmci_host *host, |
192 | u32 *words, u32 **pointer) | 196 | u32 *bytes, u32 **pointer) |
193 | { | 197 | { |
194 | struct scatterlist *sg; | 198 | struct scatterlist *sg; |
195 | 199 | ||
@@ -206,7 +210,7 @@ static inline int get_data_buffer(struct s3cmci_host *host, | |||
206 | } | 210 | } |
207 | sg = &host->mrq->data->sg[host->pio_sgptr]; | 211 | sg = &host->mrq->data->sg[host->pio_sgptr]; |
208 | 212 | ||
209 | *words = sg->length >> 2; | 213 | *bytes = sg->length; |
210 | *pointer = sg_virt(sg); | 214 | *pointer = sg_virt(sg); |
211 | 215 | ||
212 | host->pio_sgptr++; | 216 | host->pio_sgptr++; |
@@ -222,7 +226,7 @@ static inline u32 fifo_count(struct s3cmci_host *host) | |||
222 | u32 fifostat = readl(host->base + S3C2410_SDIFSTA); | 226 | u32 fifostat = readl(host->base + S3C2410_SDIFSTA); |
223 | 227 | ||
224 | fifostat &= S3C2410_SDIFSTA_COUNTMASK; | 228 | fifostat &= S3C2410_SDIFSTA_COUNTMASK; |
225 | return fifostat >> 2; | 229 | return fifostat; |
226 | } | 230 | } |
227 | 231 | ||
228 | static inline u32 fifo_free(struct s3cmci_host *host) | 232 | static inline u32 fifo_free(struct s3cmci_host *host) |
@@ -230,13 +234,15 @@ static inline u32 fifo_free(struct s3cmci_host *host) | |||
230 | u32 fifostat = readl(host->base + S3C2410_SDIFSTA); | 234 | u32 fifostat = readl(host->base + S3C2410_SDIFSTA); |
231 | 235 | ||
232 | fifostat &= S3C2410_SDIFSTA_COUNTMASK; | 236 | fifostat &= S3C2410_SDIFSTA_COUNTMASK; |
233 | return (63 - fifostat) >> 2; | 237 | return 63 - fifostat; |
234 | } | 238 | } |
235 | 239 | ||
236 | static void do_pio_read(struct s3cmci_host *host) | 240 | static void do_pio_read(struct s3cmci_host *host) |
237 | { | 241 | { |
238 | int res; | 242 | int res; |
239 | u32 fifo; | 243 | u32 fifo; |
244 | u32 *ptr; | ||
245 | u32 fifo_words; | ||
240 | void __iomem *from_ptr; | 246 | void __iomem *from_ptr; |
241 | 247 | ||
242 | /* write real prescaler to host, it might be set slow to fix */ | 248 | /* write real prescaler to host, it might be set slow to fix */ |
@@ -245,8 +251,8 @@ static void do_pio_read(struct s3cmci_host *host) | |||
245 | from_ptr = host->base + host->sdidata; | 251 | from_ptr = host->base + host->sdidata; |
246 | 252 | ||
247 | while ((fifo = fifo_count(host))) { | 253 | while ((fifo = fifo_count(host))) { |
248 | if (!host->pio_words) { | 254 | if (!host->pio_bytes) { |
249 | res = get_data_buffer(host, &host->pio_words, | 255 | res = get_data_buffer(host, &host->pio_bytes, |
250 | &host->pio_ptr); | 256 | &host->pio_ptr); |
251 | if (res) { | 257 | if (res) { |
252 | host->pio_active = XFER_NONE; | 258 | host->pio_active = XFER_NONE; |
@@ -259,26 +265,47 @@ static void do_pio_read(struct s3cmci_host *host) | |||
259 | 265 | ||
260 | dbg(host, dbg_pio, | 266 | dbg(host, dbg_pio, |
261 | "pio_read(): new target: [%i]@[%p]\n", | 267 | "pio_read(): new target: [%i]@[%p]\n", |
262 | host->pio_words, host->pio_ptr); | 268 | host->pio_bytes, host->pio_ptr); |
263 | } | 269 | } |
264 | 270 | ||
265 | dbg(host, dbg_pio, | 271 | dbg(host, dbg_pio, |
266 | "pio_read(): fifo:[%02i] buffer:[%03i] dcnt:[%08X]\n", | 272 | "pio_read(): fifo:[%02i] buffer:[%03i] dcnt:[%08X]\n", |
267 | fifo, host->pio_words, | 273 | fifo, host->pio_bytes, |
268 | readl(host->base + S3C2410_SDIDCNT)); | 274 | readl(host->base + S3C2410_SDIDCNT)); |
269 | 275 | ||
270 | if (fifo > host->pio_words) | 276 | /* If we have reached the end of the block, we can |
271 | fifo = host->pio_words; | 277 | * read a word and get 1 to 3 bytes. If we in the |
278 | * middle of the block, we have to read full words, | ||
279 | * otherwise we will write garbage, so round down to | ||
280 | * an even multiple of 4. */ | ||
281 | if (fifo >= host->pio_bytes) | ||
282 | fifo = host->pio_bytes; | ||
283 | else | ||
284 | fifo -= fifo & 3; | ||
272 | 285 | ||
273 | host->pio_words -= fifo; | 286 | host->pio_bytes -= fifo; |
274 | host->pio_count += fifo; | 287 | host->pio_count += fifo; |
275 | 288 | ||
276 | while (fifo--) | 289 | fifo_words = fifo >> 2; |
277 | *(host->pio_ptr++) = readl(from_ptr); | 290 | ptr = host->pio_ptr; |
291 | while (fifo_words--) | ||
292 | *ptr++ = readl(from_ptr); | ||
293 | host->pio_ptr = ptr; | ||
294 | |||
295 | if (fifo & 3) { | ||
296 | u32 n = fifo & 3; | ||
297 | u32 data = readl(from_ptr); | ||
298 | u8 *p = (u8 *)host->pio_ptr; | ||
299 | |||
300 | while (n--) { | ||
301 | *p++ = data; | ||
302 | data >>= 8; | ||
303 | } | ||
304 | } | ||
278 | } | 305 | } |
279 | 306 | ||
280 | if (!host->pio_words) { | 307 | if (!host->pio_bytes) { |
281 | res = get_data_buffer(host, &host->pio_words, &host->pio_ptr); | 308 | res = get_data_buffer(host, &host->pio_bytes, &host->pio_ptr); |
282 | if (res) { | 309 | if (res) { |
283 | dbg(host, dbg_pio, | 310 | dbg(host, dbg_pio, |
284 | "pio_read(): complete (no more buffers).\n"); | 311 | "pio_read(): complete (no more buffers).\n"); |
@@ -298,12 +325,13 @@ static void do_pio_write(struct s3cmci_host *host) | |||
298 | void __iomem *to_ptr; | 325 | void __iomem *to_ptr; |
299 | int res; | 326 | int res; |
300 | u32 fifo; | 327 | u32 fifo; |
328 | u32 *ptr; | ||
301 | 329 | ||
302 | to_ptr = host->base + host->sdidata; | 330 | to_ptr = host->base + host->sdidata; |
303 | 331 | ||
304 | while ((fifo = fifo_free(host))) { | 332 | while ((fifo = fifo_free(host))) { |
305 | if (!host->pio_words) { | 333 | if (!host->pio_bytes) { |
306 | res = get_data_buffer(host, &host->pio_words, | 334 | res = get_data_buffer(host, &host->pio_bytes, |
307 | &host->pio_ptr); | 335 | &host->pio_ptr); |
308 | if (res) { | 336 | if (res) { |
309 | dbg(host, dbg_pio, | 337 | dbg(host, dbg_pio, |
@@ -315,18 +343,27 @@ static void do_pio_write(struct s3cmci_host *host) | |||
315 | 343 | ||
316 | dbg(host, dbg_pio, | 344 | dbg(host, dbg_pio, |
317 | "pio_write(): new source: [%i]@[%p]\n", | 345 | "pio_write(): new source: [%i]@[%p]\n", |
318 | host->pio_words, host->pio_ptr); | 346 | host->pio_bytes, host->pio_ptr); |
319 | 347 | ||
320 | } | 348 | } |
321 | 349 | ||
322 | if (fifo > host->pio_words) | 350 | /* If we have reached the end of the block, we have to |
323 | fifo = host->pio_words; | 351 | * write exactly the remaining number of bytes. If we |
352 | * in the middle of the block, we have to write full | ||
353 | * words, so round down to an even multiple of 4. */ | ||
354 | if (fifo >= host->pio_bytes) | ||
355 | fifo = host->pio_bytes; | ||
356 | else | ||
357 | fifo -= fifo & 3; | ||
324 | 358 | ||
325 | host->pio_words -= fifo; | 359 | host->pio_bytes -= fifo; |
326 | host->pio_count += fifo; | 360 | host->pio_count += fifo; |
327 | 361 | ||
362 | fifo = (fifo + 3) >> 2; | ||
363 | ptr = host->pio_ptr; | ||
328 | while (fifo--) | 364 | while (fifo--) |
329 | writel(*(host->pio_ptr++), to_ptr); | 365 | writel(*ptr++, to_ptr); |
366 | host->pio_ptr = ptr; | ||
330 | } | 367 | } |
331 | 368 | ||
332 | enable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF); | 369 | enable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF); |
@@ -349,9 +386,9 @@ static void pio_tasklet(unsigned long data) | |||
349 | clear_imask(host); | 386 | clear_imask(host); |
350 | if (host->pio_active != XFER_NONE) { | 387 | if (host->pio_active != XFER_NONE) { |
351 | dbg(host, dbg_err, "unfinished %s " | 388 | dbg(host, dbg_err, "unfinished %s " |
352 | "- pio_count:[%u] pio_words:[%u]\n", | 389 | "- pio_count:[%u] pio_bytes:[%u]\n", |
353 | (host->pio_active == XFER_READ) ? "read" : "write", | 390 | (host->pio_active == XFER_READ) ? "read" : "write", |
354 | host->pio_count, host->pio_words); | 391 | host->pio_count, host->pio_bytes); |
355 | 392 | ||
356 | if (host->mrq->data) | 393 | if (host->mrq->data) |
357 | host->mrq->data->error = -EINVAL; | 394 | host->mrq->data->error = -EINVAL; |
@@ -812,11 +849,10 @@ static int s3cmci_setup_data(struct s3cmci_host *host, struct mmc_data *data) | |||
812 | /* We cannot deal with unaligned blocks with more than | 849 | /* We cannot deal with unaligned blocks with more than |
813 | * one block being transfered. */ | 850 | * one block being transfered. */ |
814 | 851 | ||
815 | if (data->blocks > 1) | 852 | if (data->blocks > 1) { |
853 | pr_warning("%s: can't do non-word sized block transfers (blksz %d)\n", __func__, data->blksz); | ||
816 | return -EINVAL; | 854 | return -EINVAL; |
817 | 855 | } | |
818 | /* No support yet for non-word block transfers. */ | ||
819 | return -EINVAL; | ||
820 | } | 856 | } |
821 | 857 | ||
822 | while (readl(host->base + S3C2410_SDIDSTA) & | 858 | while (readl(host->base + S3C2410_SDIDSTA) & |
@@ -896,7 +932,7 @@ static int s3cmci_prepare_pio(struct s3cmci_host *host, struct mmc_data *data) | |||
896 | BUG_ON((data->flags & BOTH_DIR) == BOTH_DIR); | 932 | BUG_ON((data->flags & BOTH_DIR) == BOTH_DIR); |
897 | 933 | ||
898 | host->pio_sgptr = 0; | 934 | host->pio_sgptr = 0; |
899 | host->pio_words = 0; | 935 | host->pio_bytes = 0; |
900 | host->pio_count = 0; | 936 | host->pio_count = 0; |
901 | host->pio_active = rw ? XFER_WRITE : XFER_READ; | 937 | host->pio_active = rw ? XFER_WRITE : XFER_READ; |
902 | 938 | ||
@@ -1033,10 +1069,33 @@ static void s3cmci_request(struct mmc_host *mmc, struct mmc_request *mrq) | |||
1033 | s3cmci_send_request(mmc); | 1069 | s3cmci_send_request(mmc); |
1034 | } | 1070 | } |
1035 | 1071 | ||
1072 | static void s3cmci_set_clk(struct s3cmci_host *host, struct mmc_ios *ios) | ||
1073 | { | ||
1074 | u32 mci_psc; | ||
1075 | |||
1076 | /* Set clock */ | ||
1077 | for (mci_psc = 0; mci_psc < 255; mci_psc++) { | ||
1078 | host->real_rate = host->clk_rate / (host->clk_div*(mci_psc+1)); | ||
1079 | |||
1080 | if (host->real_rate <= ios->clock) | ||
1081 | break; | ||
1082 | } | ||
1083 | |||
1084 | if (mci_psc > 255) | ||
1085 | mci_psc = 255; | ||
1086 | |||
1087 | host->prescaler = mci_psc; | ||
1088 | writel(host->prescaler, host->base + S3C2410_SDIPRE); | ||
1089 | |||
1090 | /* If requested clock is 0, real_rate will be 0, too */ | ||
1091 | if (ios->clock == 0) | ||
1092 | host->real_rate = 0; | ||
1093 | } | ||
1094 | |||
1036 | static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) | 1095 | static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) |
1037 | { | 1096 | { |
1038 | struct s3cmci_host *host = mmc_priv(mmc); | 1097 | struct s3cmci_host *host = mmc_priv(mmc); |
1039 | u32 mci_psc, mci_con; | 1098 | u32 mci_con; |
1040 | 1099 | ||
1041 | /* Set the power state */ | 1100 | /* Set the power state */ |
1042 | 1101 | ||
@@ -1074,23 +1133,7 @@ static void s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) | |||
1074 | break; | 1133 | break; |
1075 | } | 1134 | } |
1076 | 1135 | ||
1077 | /* Set clock */ | 1136 | s3cmci_set_clk(host, ios); |
1078 | for (mci_psc = 0; mci_psc < 255; mci_psc++) { | ||
1079 | host->real_rate = host->clk_rate / (host->clk_div*(mci_psc+1)); | ||
1080 | |||
1081 | if (host->real_rate <= ios->clock) | ||
1082 | break; | ||
1083 | } | ||
1084 | |||
1085 | if (mci_psc > 255) | ||
1086 | mci_psc = 255; | ||
1087 | |||
1088 | host->prescaler = mci_psc; | ||
1089 | writel(host->prescaler, host->base + S3C2410_SDIPRE); | ||
1090 | |||
1091 | /* If requested clock is 0, real_rate will be 0, too */ | ||
1092 | if (ios->clock == 0) | ||
1093 | host->real_rate = 0; | ||
1094 | 1137 | ||
1095 | /* Set CLOCK_ENABLE */ | 1138 | /* Set CLOCK_ENABLE */ |
1096 | if (ios->clock) | 1139 | if (ios->clock) |
@@ -1148,6 +1191,61 @@ static struct s3c24xx_mci_pdata s3cmci_def_pdata = { | |||
1148 | * checks. Any zero fields to ensure reaonable defaults are picked. */ | 1191 | * checks. Any zero fields to ensure reaonable defaults are picked. */ |
1149 | }; | 1192 | }; |
1150 | 1193 | ||
1194 | #ifdef CONFIG_CPU_FREQ | ||
1195 | |||
1196 | static int s3cmci_cpufreq_transition(struct notifier_block *nb, | ||
1197 | unsigned long val, void *data) | ||
1198 | { | ||
1199 | struct s3cmci_host *host; | ||
1200 | struct mmc_host *mmc; | ||
1201 | unsigned long newclk; | ||
1202 | unsigned long flags; | ||
1203 | |||
1204 | host = container_of(nb, struct s3cmci_host, freq_transition); | ||
1205 | newclk = clk_get_rate(host->clk); | ||
1206 | mmc = host->mmc; | ||
1207 | |||
1208 | if ((val == CPUFREQ_PRECHANGE && newclk > host->clk_rate) || | ||
1209 | (val == CPUFREQ_POSTCHANGE && newclk < host->clk_rate)) { | ||
1210 | spin_lock_irqsave(&mmc->lock, flags); | ||
1211 | |||
1212 | host->clk_rate = newclk; | ||
1213 | |||
1214 | if (mmc->ios.power_mode != MMC_POWER_OFF && | ||
1215 | mmc->ios.clock != 0) | ||
1216 | s3cmci_set_clk(host, &mmc->ios); | ||
1217 | |||
1218 | spin_unlock_irqrestore(&mmc->lock, flags); | ||
1219 | } | ||
1220 | |||
1221 | return 0; | ||
1222 | } | ||
1223 | |||
1224 | static inline int s3cmci_cpufreq_register(struct s3cmci_host *host) | ||
1225 | { | ||
1226 | host->freq_transition.notifier_call = s3cmci_cpufreq_transition; | ||
1227 | |||
1228 | return cpufreq_register_notifier(&host->freq_transition, | ||
1229 | CPUFREQ_TRANSITION_NOTIFIER); | ||
1230 | } | ||
1231 | |||
1232 | static inline void s3cmci_cpufreq_deregister(struct s3cmci_host *host) | ||
1233 | { | ||
1234 | cpufreq_unregister_notifier(&host->freq_transition, | ||
1235 | CPUFREQ_TRANSITION_NOTIFIER); | ||
1236 | } | ||
1237 | |||
1238 | #else | ||
1239 | static inline int s3cmci_cpufreq_register(struct s3cmci_host *host) | ||
1240 | { | ||
1241 | return 0; | ||
1242 | } | ||
1243 | |||
1244 | static inline void s3cmci_cpufreq_deregister(struct s3cmci_host *host) | ||
1245 | { | ||
1246 | } | ||
1247 | #endif | ||
1248 | |||
1151 | static int __devinit s3cmci_probe(struct platform_device *pdev, int is2440) | 1249 | static int __devinit s3cmci_probe(struct platform_device *pdev, int is2440) |
1152 | { | 1250 | { |
1153 | struct s3cmci_host *host; | 1251 | struct s3cmci_host *host; |
@@ -1298,10 +1396,16 @@ static int __devinit s3cmci_probe(struct platform_device *pdev, int is2440) | |||
1298 | (host->is2440?"2440":""), | 1396 | (host->is2440?"2440":""), |
1299 | host->base, host->irq, host->irq_cd, host->dma); | 1397 | host->base, host->irq, host->irq_cd, host->dma); |
1300 | 1398 | ||
1399 | ret = s3cmci_cpufreq_register(host); | ||
1400 | if (ret) { | ||
1401 | dev_err(&pdev->dev, "failed to register cpufreq\n"); | ||
1402 | goto free_dmabuf; | ||
1403 | } | ||
1404 | |||
1301 | ret = mmc_add_host(mmc); | 1405 | ret = mmc_add_host(mmc); |
1302 | if (ret) { | 1406 | if (ret) { |
1303 | dev_err(&pdev->dev, "failed to add mmc host.\n"); | 1407 | dev_err(&pdev->dev, "failed to add mmc host.\n"); |
1304 | goto free_dmabuf; | 1408 | goto free_cpufreq; |
1305 | } | 1409 | } |
1306 | 1410 | ||
1307 | platform_set_drvdata(pdev, mmc); | 1411 | platform_set_drvdata(pdev, mmc); |
@@ -1309,6 +1413,9 @@ static int __devinit s3cmci_probe(struct platform_device *pdev, int is2440) | |||
1309 | 1413 | ||
1310 | return 0; | 1414 | return 0; |
1311 | 1415 | ||
1416 | free_cpufreq: | ||
1417 | s3cmci_cpufreq_deregister(host); | ||
1418 | |||
1312 | free_dmabuf: | 1419 | free_dmabuf: |
1313 | clk_disable(host->clk); | 1420 | clk_disable(host->clk); |
1314 | 1421 | ||
@@ -1342,6 +1449,7 @@ static void s3cmci_shutdown(struct platform_device *pdev) | |||
1342 | if (host->irq_cd >= 0) | 1449 | if (host->irq_cd >= 0) |
1343 | free_irq(host->irq_cd, host); | 1450 | free_irq(host->irq_cd, host); |
1344 | 1451 | ||
1452 | s3cmci_cpufreq_deregister(host); | ||
1345 | mmc_remove_host(mmc); | 1453 | mmc_remove_host(mmc); |
1346 | clk_disable(host->clk); | 1454 | clk_disable(host->clk); |
1347 | } | 1455 | } |
@@ -1455,7 +1563,7 @@ module_exit(s3cmci_exit); | |||
1455 | 1563 | ||
1456 | MODULE_DESCRIPTION("Samsung S3C MMC/SD Card Interface driver"); | 1564 | MODULE_DESCRIPTION("Samsung S3C MMC/SD Card Interface driver"); |
1457 | MODULE_LICENSE("GPL v2"); | 1565 | MODULE_LICENSE("GPL v2"); |
1458 | MODULE_AUTHOR("Thomas Kleffel <tk@maintech.de>"); | 1566 | MODULE_AUTHOR("Thomas Kleffel <tk@maintech.de>, Ben Dooks <ben-linux@fluff.org>"); |
1459 | MODULE_ALIAS("platform:s3c2410-sdi"); | 1567 | MODULE_ALIAS("platform:s3c2410-sdi"); |
1460 | MODULE_ALIAS("platform:s3c2412-sdi"); | 1568 | MODULE_ALIAS("platform:s3c2412-sdi"); |
1461 | MODULE_ALIAS("platform:s3c2440-sdi"); | 1569 | MODULE_ALIAS("platform:s3c2440-sdi"); |