Commit | Line | Data |
---|---|---|
9d2bd738 SM |
1 | /* |
2 | * linux/drivers/mmc/host/msm_sdcc.c - Qualcomm MSM 7X00A SDCC Driver | |
3 | * | |
4 | * Copyright (C) 2007 Google Inc, | |
5 | * Copyright (C) 2003 Deep Blue Solutions, Ltd, All Rights Reserved. | |
56a8b5b8 | 6 | * Copyright (C) 2009, Code Aurora Forum. All Rights Reserved. |
9d2bd738 SM |
7 | * |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License version 2 as | |
10 | * published by the Free Software Foundation. | |
11 | * | |
12 | * Based on mmci.c | |
13 | * | |
14 | * Author: San Mehat (san@android.com) | |
15 | * | |
16 | */ | |
17 | ||
18 | #include <linux/module.h> | |
19 | #include <linux/moduleparam.h> | |
20 | #include <linux/init.h> | |
21 | #include <linux/ioport.h> | |
22 | #include <linux/device.h> | |
23 | #include <linux/interrupt.h> | |
24 | #include <linux/delay.h> | |
25 | #include <linux/err.h> | |
26 | #include <linux/highmem.h> | |
27 | #include <linux/log2.h> | |
28 | #include <linux/mmc/host.h> | |
29 | #include <linux/mmc/card.h> | |
b3fa5791 | 30 | #include <linux/mmc/sdio.h> |
9d2bd738 SM |
31 | #include <linux/clk.h> |
32 | #include <linux/scatterlist.h> | |
33 | #include <linux/platform_device.h> | |
34 | #include <linux/dma-mapping.h> | |
35 | #include <linux/debugfs.h> | |
36 | #include <linux/io.h> | |
37 | #include <linux/memory.h> | |
38 | ||
39 | #include <asm/cacheflush.h> | |
40 | #include <asm/div64.h> | |
41 | #include <asm/sizes.h> | |
42 | ||
3989d178 | 43 | #include <mach/mmc.h> |
9d2bd738 SM |
44 | #include <mach/msm_iomap.h> |
45 | #include <mach/dma.h> | |
9d2bd738 | 46 | |
9d2bd738 SM |
47 | #include "msm_sdcc.h" |
48 | ||
49 | #define DRIVER_NAME "msm-sdcc" | |
50 | ||
c7fc9370 | 51 | #define BUSCLK_TIMEOUT (HZ) |
9d2bd738 SM |
52 | static unsigned int msmsdcc_fmin = 144000; |
53 | static unsigned int msmsdcc_fmax = 50000000; | |
54 | static unsigned int msmsdcc_4bit = 1; | |
55 | static unsigned int msmsdcc_pwrsave = 1; | |
56 | static unsigned int msmsdcc_piopoll = 1; | |
57 | static unsigned int msmsdcc_sdioirq; | |
58 | ||
59 | #define PIO_SPINMAX 30 | |
60 | #define CMD_SPINMAX 20 | |
61 | ||
865c8064 | 62 | |
c7fc9370 SM |
63 | static inline void |
64 | msmsdcc_disable_clocks(struct msmsdcc_host *host, int deferr) | |
865c8064 | 65 | { |
c7fc9370 | 66 | WARN_ON(!host->clks_on); |
8b1c2ba2 | 67 | |
c7fc9370 SM |
68 | if (deferr) { |
69 | mod_timer(&host->busclk_timer, jiffies + BUSCLK_TIMEOUT); | |
865c8064 | 70 | } else { |
c7fc9370 SM |
71 | del_timer_sync(&host->busclk_timer); |
72 | // dev_info(mmc_dev(host->mmc), "Immediate clock shutdown\n"); | |
865c8064 SM |
73 | clk_disable(host->clk); |
74 | clk_disable(host->pclk); | |
75 | host->clks_on = 0; | |
76 | } | |
c7fc9370 SM |
77 | } |
78 | ||
79 | static inline int | |
80 | msmsdcc_enable_clocks(struct msmsdcc_host *host) | |
81 | { | |
82 | int rc; | |
83 | ||
84 | WARN_ON(host->clks_on); | |
85 | ||
86 | del_timer_sync(&host->busclk_timer); | |
87 | ||
88 | rc = clk_enable(host->pclk); | |
89 | if (rc) | |
90 | return rc; | |
91 | rc = clk_enable(host->clk); | |
92 | if (rc) { | |
93 | clk_disable(host->pclk); | |
94 | return rc; | |
95 | } | |
96 | udelay(1 + ((3 * USEC_PER_SEC) / | |
97 | (host->clk_rate ? host->clk_rate : msmsdcc_fmin))); | |
98 | host->clks_on = 1; | |
865c8064 SM |
99 | return 0; |
100 | } | |
101 | ||
8b1c2ba2 SM |
102 | static inline unsigned int |
103 | msmsdcc_readl(struct msmsdcc_host *host, unsigned int reg) | |
104 | { | |
105 | return readl(host->base + reg); | |
106 | } | |
107 | ||
108 | static inline void | |
109 | msmsdcc_writel(struct msmsdcc_host *host, u32 data, unsigned int reg) | |
110 | { | |
111 | writel(data, host->base + reg); | |
112 | /* 3 clk delay required! */ | |
113 | udelay(1 + ((3 * USEC_PER_SEC) / | |
114 | (host->clk_rate ? host->clk_rate : msmsdcc_fmin))); | |
115 | } | |
865c8064 | 116 | |
9d2bd738 SM |
117 | static void |
118 | msmsdcc_start_command(struct msmsdcc_host *host, struct mmc_command *cmd, | |
119 | u32 c); | |
120 | ||
121 | static void | |
122 | msmsdcc_request_end(struct msmsdcc_host *host, struct mmc_request *mrq) | |
123 | { | |
9d2bd738 SM |
124 | BUG_ON(host->curr.data); |
125 | ||
126 | host->curr.mrq = NULL; | |
127 | host->curr.cmd = NULL; | |
128 | ||
129 | if (mrq->data) | |
130 | mrq->data->bytes_xfered = host->curr.data_xfered; | |
131 | if (mrq->cmd->error == -ETIMEDOUT) | |
132 | mdelay(5); | |
133 | ||
c7fc9370 | 134 | msmsdcc_disable_clocks(host, 1); |
9d2bd738 SM |
135 | /* |
136 | * Need to drop the host lock here; mmc_request_done may call | |
137 | * back into the driver... | |
138 | */ | |
139 | spin_unlock(&host->lock); | |
140 | mmc_request_done(host->mmc, mrq); | |
141 | spin_lock(&host->lock); | |
142 | } | |
143 | ||
144 | static void | |
145 | msmsdcc_stop_data(struct msmsdcc_host *host) | |
146 | { | |
9d2bd738 SM |
147 | host->curr.data = NULL; |
148 | host->curr.got_dataend = host->curr.got_datablkend = 0; | |
149 | } | |
150 | ||
151 | uint32_t msmsdcc_fifo_addr(struct msmsdcc_host *host) | |
152 | { | |
75d14528 JP |
153 | switch (host->pdev_id) { |
154 | case 1: | |
9d2bd738 | 155 | return MSM_SDC1_PHYS + MMCIFIFO; |
75d14528 | 156 | case 2: |
9d2bd738 | 157 | return MSM_SDC2_PHYS + MMCIFIFO; |
75d14528 | 158 | case 3: |
9d2bd738 | 159 | return MSM_SDC3_PHYS + MMCIFIFO; |
75d14528 | 160 | case 4: |
9d2bd738 | 161 | return MSM_SDC4_PHYS + MMCIFIFO; |
75d14528 JP |
162 | } |
163 | BUG(); | |
9d2bd738 SM |
164 | return 0; |
165 | } | |
166 | ||
56a8b5b8 SM |
167 | static inline void |
168 | msmsdcc_start_command_exec(struct msmsdcc_host *host, u32 arg, u32 c) { | |
169 | msmsdcc_writel(host, arg, MMCIARGUMENT); | |
170 | msmsdcc_writel(host, c, MMCICOMMAND); | |
171 | } | |
172 | ||
173 | static void | |
174 | msmsdcc_dma_exec_func(struct msm_dmov_cmd *cmd) | |
175 | { | |
176 | struct msmsdcc_host *host = (struct msmsdcc_host *)cmd->data; | |
177 | ||
178 | writel(host->cmd_timeout, host->base + MMCIDATATIMER); | |
179 | writel((unsigned int)host->curr.xfer_size, host->base + MMCIDATALENGTH); | |
180 | writel(host->cmd_pio_irqmask, host->base + MMCIMASK1); | |
181 | writel(host->cmd_datactrl, host->base + MMCIDATACTRL); | |
182 | ||
183 | if (host->cmd_cmd) { | |
184 | msmsdcc_start_command_exec(host, | |
185 | (u32)host->cmd_cmd->arg, (u32)host->cmd_c); | |
186 | } | |
187 | host->dma.active = 1; | |
188 | } | |
189 | ||
9d2bd738 SM |
190 | static void |
191 | msmsdcc_dma_complete_func(struct msm_dmov_cmd *cmd, | |
192 | unsigned int result, | |
193 | struct msm_dmov_errdata *err) | |
194 | { | |
195 | struct msmsdcc_dma_data *dma_data = | |
196 | container_of(cmd, struct msmsdcc_dma_data, hdr); | |
197 | struct msmsdcc_host *host = dma_data->host; | |
198 | unsigned long flags; | |
199 | struct mmc_request *mrq; | |
200 | ||
201 | spin_lock_irqsave(&host->lock, flags); | |
56a8b5b8 SM |
202 | host->dma.active = 0; |
203 | ||
9d2bd738 SM |
204 | mrq = host->curr.mrq; |
205 | BUG_ON(!mrq); | |
206 | ||
207 | if (!(result & DMOV_RSLT_VALID)) { | |
0a7ff7c7 | 208 | pr_err("msmsdcc: Invalid DataMover result\n"); |
9d2bd738 SM |
209 | goto out; |
210 | } | |
211 | ||
212 | if (result & DMOV_RSLT_DONE) { | |
213 | host->curr.data_xfered = host->curr.xfer_size; | |
214 | } else { | |
215 | /* Error or flush */ | |
216 | if (result & DMOV_RSLT_ERROR) | |
0a7ff7c7 | 217 | pr_err("%s: DMA error (0x%.8x)\n", |
9d2bd738 SM |
218 | mmc_hostname(host->mmc), result); |
219 | if (result & DMOV_RSLT_FLUSH) | |
0a7ff7c7 | 220 | pr_err("%s: DMA channel flushed (0x%.8x)\n", |
9d2bd738 SM |
221 | mmc_hostname(host->mmc), result); |
222 | if (err) | |
0a7ff7c7 | 223 | pr_err("Flush data: %.8x %.8x %.8x %.8x %.8x %.8x\n", |
9d2bd738 SM |
224 | err->flush[0], err->flush[1], err->flush[2], |
225 | err->flush[3], err->flush[4], err->flush[5]); | |
226 | if (!mrq->data->error) | |
227 | mrq->data->error = -EIO; | |
228 | } | |
9d2bd738 SM |
229 | dma_unmap_sg(mmc_dev(host->mmc), host->dma.sg, host->dma.num_ents, |
230 | host->dma.dir); | |
231 | ||
232 | if (host->curr.user_pages) { | |
233 | struct scatterlist *sg = host->dma.sg; | |
234 | int i; | |
235 | ||
75d14528 JP |
236 | for (i = 0; i < host->dma.num_ents; i++) |
237 | flush_dcache_page(sg_page(sg++)); | |
9d2bd738 SM |
238 | } |
239 | ||
240 | host->dma.sg = NULL; | |
56a8b5b8 | 241 | host->dma.busy = 0; |
9d2bd738 SM |
242 | |
243 | if ((host->curr.got_dataend && host->curr.got_datablkend) | |
244 | || mrq->data->error) { | |
245 | ||
246 | /* | |
247 | * If we've already gotten our DATAEND / DATABLKEND | |
248 | * for this request, then complete it through here. | |
249 | */ | |
250 | msmsdcc_stop_data(host); | |
251 | ||
252 | if (!mrq->data->error) | |
253 | host->curr.data_xfered = host->curr.xfer_size; | |
254 | if (!mrq->data->stop || mrq->cmd->error) { | |
9d2bd738 SM |
255 | host->curr.mrq = NULL; |
256 | host->curr.cmd = NULL; | |
257 | mrq->data->bytes_xfered = host->curr.data_xfered; | |
258 | ||
259 | spin_unlock_irqrestore(&host->lock, flags); | |
c7fc9370 | 260 | msmsdcc_disable_clocks(host, 1); |
9d2bd738 SM |
261 | mmc_request_done(host->mmc, mrq); |
262 | return; | |
263 | } else | |
264 | msmsdcc_start_command(host, mrq->data->stop, 0); | |
265 | } | |
266 | ||
267 | out: | |
268 | spin_unlock_irqrestore(&host->lock, flags); | |
269 | return; | |
270 | } | |
271 | ||
272 | static int validate_dma(struct msmsdcc_host *host, struct mmc_data *data) | |
273 | { | |
274 | if (host->dma.channel == -1) | |
275 | return -ENOENT; | |
276 | ||
277 | if ((data->blksz * data->blocks) < MCI_FIFOSIZE) | |
278 | return -EINVAL; | |
279 | if ((data->blksz * data->blocks) % MCI_FIFOSIZE) | |
280 | return -EINVAL; | |
281 | return 0; | |
282 | } | |
283 | ||
284 | static int msmsdcc_config_dma(struct msmsdcc_host *host, struct mmc_data *data) | |
285 | { | |
286 | struct msmsdcc_nc_dmadata *nc; | |
287 | dmov_box *box; | |
288 | uint32_t rows; | |
289 | uint32_t crci; | |
290 | unsigned int n; | |
291 | int i, rc; | |
292 | struct scatterlist *sg = data->sg; | |
293 | ||
294 | rc = validate_dma(host, data); | |
295 | if (rc) | |
296 | return rc; | |
297 | ||
298 | host->dma.sg = data->sg; | |
299 | host->dma.num_ents = data->sg_len; | |
300 | ||
56a8b5b8 SM |
301 | BUG_ON(host->dma.num_ents > NR_SG); /* Prevent memory corruption */ |
302 | ||
9d2bd738 SM |
303 | nc = host->dma.nc; |
304 | ||
75d14528 JP |
305 | switch (host->pdev_id) { |
306 | case 1: | |
9d2bd738 | 307 | crci = MSMSDCC_CRCI_SDC1; |
75d14528 JP |
308 | break; |
309 | case 2: | |
9d2bd738 | 310 | crci = MSMSDCC_CRCI_SDC2; |
75d14528 JP |
311 | break; |
312 | case 3: | |
9d2bd738 | 313 | crci = MSMSDCC_CRCI_SDC3; |
75d14528 JP |
314 | break; |
315 | case 4: | |
9d2bd738 | 316 | crci = MSMSDCC_CRCI_SDC4; |
75d14528 JP |
317 | break; |
318 | default: | |
9d2bd738 SM |
319 | host->dma.sg = NULL; |
320 | host->dma.num_ents = 0; | |
321 | return -ENOENT; | |
322 | } | |
323 | ||
324 | if (data->flags & MMC_DATA_READ) | |
325 | host->dma.dir = DMA_FROM_DEVICE; | |
326 | else | |
327 | host->dma.dir = DMA_TO_DEVICE; | |
328 | ||
329 | host->curr.user_pages = 0; | |
330 | ||
9d2bd738 SM |
331 | box = &nc->cmd[0]; |
332 | for (i = 0; i < host->dma.num_ents; i++) { | |
333 | box->cmd = CMD_MODE_BOX; | |
334 | ||
56a8b5b8 SM |
335 | /* Initialize sg dma address */ |
336 | sg->dma_address = page_to_dma(mmc_dev(host->mmc), sg_page(sg)) | |
337 | + sg->offset; | |
338 | ||
339 | if (i == (host->dma.num_ents - 1)) | |
9d2bd738 SM |
340 | box->cmd |= CMD_LC; |
341 | rows = (sg_dma_len(sg) % MCI_FIFOSIZE) ? | |
342 | (sg_dma_len(sg) / MCI_FIFOSIZE) + 1 : | |
343 | (sg_dma_len(sg) / MCI_FIFOSIZE) ; | |
344 | ||
345 | if (data->flags & MMC_DATA_READ) { | |
346 | box->src_row_addr = msmsdcc_fifo_addr(host); | |
347 | box->dst_row_addr = sg_dma_address(sg); | |
348 | ||
349 | box->src_dst_len = (MCI_FIFOSIZE << 16) | | |
350 | (MCI_FIFOSIZE); | |
351 | box->row_offset = MCI_FIFOSIZE; | |
352 | ||
353 | box->num_rows = rows * ((1 << 16) + 1); | |
354 | box->cmd |= CMD_SRC_CRCI(crci); | |
355 | } else { | |
356 | box->src_row_addr = sg_dma_address(sg); | |
357 | box->dst_row_addr = msmsdcc_fifo_addr(host); | |
358 | ||
359 | box->src_dst_len = (MCI_FIFOSIZE << 16) | | |
360 | (MCI_FIFOSIZE); | |
361 | box->row_offset = (MCI_FIFOSIZE << 16); | |
362 | ||
363 | box->num_rows = rows * ((1 << 16) + 1); | |
364 | box->cmd |= CMD_DST_CRCI(crci); | |
365 | } | |
366 | box++; | |
367 | sg++; | |
368 | } | |
369 | ||
370 | /* location of command block must be 64 bit aligned */ | |
371 | BUG_ON(host->dma.cmd_busaddr & 0x07); | |
372 | ||
373 | nc->cmdptr = (host->dma.cmd_busaddr >> 3) | CMD_PTR_LP; | |
374 | host->dma.hdr.cmdptr = DMOV_CMD_PTR_LIST | | |
375 | DMOV_CMD_ADDR(host->dma.cmdptr_busaddr); | |
376 | host->dma.hdr.complete_func = msmsdcc_dma_complete_func; | |
377 | ||
56a8b5b8 SM |
378 | n = dma_map_sg(mmc_dev(host->mmc), host->dma.sg, |
379 | host->dma.num_ents, host->dma.dir); | |
380 | /* dsb inside dma_map_sg will write nc out to mem as well */ | |
381 | ||
382 | if (n != host->dma.num_ents) { | |
383 | printk(KERN_ERR "%s: Unable to map in all sg elements\n", | |
384 | mmc_hostname(host->mmc)); | |
385 | host->dma.sg = NULL; | |
386 | host->dma.num_ents = 0; | |
387 | return -ENOMEM; | |
388 | } | |
389 | ||
390 | return 0; | |
391 | } | |
392 | ||
393 | static int | |
394 | snoop_cccr_abort(struct mmc_command *cmd) | |
395 | { | |
396 | if ((cmd->opcode == 52) && | |
397 | (cmd->arg & 0x80000000) && | |
398 | (((cmd->arg >> 9) & 0x1ffff) == SDIO_CCCR_ABORT)) | |
399 | return 1; | |
9d2bd738 SM |
400 | return 0; |
401 | } | |
402 | ||
403 | static void | |
56a8b5b8 SM |
404 | msmsdcc_start_command_deferred(struct msmsdcc_host *host, |
405 | struct mmc_command *cmd, u32 *c) | |
406 | { | |
407 | *c |= (cmd->opcode | MCI_CPSM_ENABLE); | |
408 | ||
409 | if (cmd->flags & MMC_RSP_PRESENT) { | |
410 | if (cmd->flags & MMC_RSP_136) | |
411 | *c |= MCI_CPSM_LONGRSP; | |
412 | *c |= MCI_CPSM_RESPONSE; | |
413 | } | |
414 | ||
415 | if (/*interrupt*/0) | |
416 | *c |= MCI_CPSM_INTERRUPT; | |
417 | ||
418 | if ((((cmd->opcode == 17) || (cmd->opcode == 18)) || | |
419 | ((cmd->opcode == 24) || (cmd->opcode == 25))) || | |
420 | (cmd->opcode == 53)) | |
421 | *c |= MCI_CSPM_DATCMD; | |
422 | ||
423 | if (cmd == cmd->mrq->stop) | |
424 | *c |= MCI_CSPM_MCIABORT; | |
425 | ||
426 | if (snoop_cccr_abort(cmd)) | |
427 | *c |= MCI_CSPM_MCIABORT; | |
428 | ||
429 | if (host->curr.cmd != NULL) { | |
430 | printk(KERN_ERR "%s: Overlapping command requests\n", | |
431 | mmc_hostname(host->mmc)); | |
432 | } | |
433 | host->curr.cmd = cmd; | |
434 | } | |
435 | ||
436 | static void | |
437 | msmsdcc_start_data(struct msmsdcc_host *host, struct mmc_data *data, | |
438 | struct mmc_command *cmd, u32 c) | |
9d2bd738 SM |
439 | { |
440 | unsigned int datactrl, timeout; | |
441 | unsigned long long clks; | |
9d2bd738 SM |
442 | unsigned int pio_irqmask = 0; |
443 | ||
444 | host->curr.data = data; | |
445 | host->curr.xfer_size = data->blksz * data->blocks; | |
446 | host->curr.xfer_remain = host->curr.xfer_size; | |
447 | host->curr.data_xfered = 0; | |
448 | host->curr.got_dataend = 0; | |
449 | host->curr.got_datablkend = 0; | |
450 | ||
451 | memset(&host->pio, 0, sizeof(host->pio)); | |
452 | ||
9d2bd738 SM |
453 | datactrl = MCI_DPSM_ENABLE | (data->blksz << 4); |
454 | ||
455 | if (!msmsdcc_config_dma(host, data)) | |
456 | datactrl |= MCI_DPSM_DMAENABLE; | |
457 | else { | |
458 | host->pio.sg = data->sg; | |
459 | host->pio.sg_len = data->sg_len; | |
460 | host->pio.sg_off = 0; | |
461 | ||
462 | if (data->flags & MMC_DATA_READ) { | |
463 | pio_irqmask = MCI_RXFIFOHALFFULLMASK; | |
464 | if (host->curr.xfer_remain < MCI_FIFOSIZE) | |
465 | pio_irqmask |= MCI_RXDATAAVLBLMASK; | |
466 | } else | |
467 | pio_irqmask = MCI_TXFIFOHALFEMPTYMASK; | |
468 | } | |
469 | ||
470 | if (data->flags & MMC_DATA_READ) | |
471 | datactrl |= MCI_DPSM_DIRECTION; | |
472 | ||
56a8b5b8 SM |
473 | clks = (unsigned long long)data->timeout_ns * host->clk_rate; |
474 | do_div(clks, NSEC_PER_SEC); | |
475 | timeout = data->timeout_clks + (unsigned int)clks*2 ; | |
9d2bd738 SM |
476 | |
477 | if (datactrl & MCI_DPSM_DMAENABLE) { | |
56a8b5b8 SM |
478 | /* Save parameters for the exec function */ |
479 | host->cmd_timeout = timeout; | |
480 | host->cmd_pio_irqmask = pio_irqmask; | |
481 | host->cmd_datactrl = datactrl; | |
482 | host->cmd_cmd = cmd; | |
483 | ||
484 | host->dma.hdr.execute_func = msmsdcc_dma_exec_func; | |
485 | host->dma.hdr.data = (void *)host; | |
9d2bd738 | 486 | host->dma.busy = 1; |
56a8b5b8 SM |
487 | |
488 | if (cmd) { | |
489 | msmsdcc_start_command_deferred(host, cmd, &c); | |
490 | host->cmd_c = c; | |
491 | } | |
9d2bd738 | 492 | msm_dmov_enqueue_cmd(host->dma.channel, &host->dma.hdr); |
56a8b5b8 SM |
493 | } else { |
494 | msmsdcc_writel(host, timeout, MMCIDATATIMER); | |
9d2bd738 | 495 | |
56a8b5b8 SM |
496 | msmsdcc_writel(host, host->curr.xfer_size, MMCIDATALENGTH); |
497 | ||
498 | msmsdcc_writel(host, pio_irqmask, MMCIMASK1); | |
499 | msmsdcc_writel(host, datactrl, MMCIDATACTRL); | |
500 | ||
501 | if (cmd) { | |
502 | /* Daisy-chain the command if requested */ | |
503 | msmsdcc_start_command(host, cmd, c); | |
504 | } | |
505 | } | |
b3fa5791 SM |
506 | } |
507 | ||
9d2bd738 SM |
508 | static void |
509 | msmsdcc_start_command(struct msmsdcc_host *host, struct mmc_command *cmd, u32 c) | |
510 | { | |
9d2bd738 SM |
511 | if (cmd == cmd->mrq->stop) |
512 | c |= MCI_CSPM_MCIABORT; | |
513 | ||
9d2bd738 SM |
514 | host->stats.cmds++; |
515 | ||
56a8b5b8 SM |
516 | msmsdcc_start_command_deferred(host, cmd, &c); |
517 | msmsdcc_start_command_exec(host, cmd->arg, c); | |
9d2bd738 SM |
518 | } |
519 | ||
520 | static void | |
521 | msmsdcc_data_err(struct msmsdcc_host *host, struct mmc_data *data, | |
522 | unsigned int status) | |
523 | { | |
524 | if (status & MCI_DATACRCFAIL) { | |
0a7ff7c7 JP |
525 | pr_err("%s: Data CRC error\n", mmc_hostname(host->mmc)); |
526 | pr_err("%s: opcode 0x%.8x\n", __func__, | |
9d2bd738 | 527 | data->mrq->cmd->opcode); |
0a7ff7c7 | 528 | pr_err("%s: blksz %d, blocks %d\n", __func__, |
9d2bd738 SM |
529 | data->blksz, data->blocks); |
530 | data->error = -EILSEQ; | |
531 | } else if (status & MCI_DATATIMEOUT) { | |
0a7ff7c7 | 532 | pr_err("%s: Data timeout\n", mmc_hostname(host->mmc)); |
9d2bd738 SM |
533 | data->error = -ETIMEDOUT; |
534 | } else if (status & MCI_RXOVERRUN) { | |
0a7ff7c7 | 535 | pr_err("%s: RX overrun\n", mmc_hostname(host->mmc)); |
9d2bd738 SM |
536 | data->error = -EIO; |
537 | } else if (status & MCI_TXUNDERRUN) { | |
0a7ff7c7 | 538 | pr_err("%s: TX underrun\n", mmc_hostname(host->mmc)); |
9d2bd738 SM |
539 | data->error = -EIO; |
540 | } else { | |
0a7ff7c7 JP |
541 | pr_err("%s: Unknown error (0x%.8x)\n", |
542 | mmc_hostname(host->mmc), status); | |
9d2bd738 SM |
543 | data->error = -EIO; |
544 | } | |
545 | } | |
546 | ||
547 | ||
548 | static int | |
549 | msmsdcc_pio_read(struct msmsdcc_host *host, char *buffer, unsigned int remain) | |
550 | { | |
9d2bd738 SM |
551 | uint32_t *ptr = (uint32_t *) buffer; |
552 | int count = 0; | |
553 | ||
8b1c2ba2 SM |
554 | while (msmsdcc_readl(host, MMCISTATUS) & MCI_RXDATAAVLBL) { |
555 | *ptr = msmsdcc_readl(host, MMCIFIFO + (count % MCI_FIFOSIZE)); | |
9d2bd738 SM |
556 | ptr++; |
557 | count += sizeof(uint32_t); | |
558 | ||
559 | remain -= sizeof(uint32_t); | |
560 | if (remain == 0) | |
561 | break; | |
562 | } | |
563 | return count; | |
564 | } | |
565 | ||
566 | static int | |
567 | msmsdcc_pio_write(struct msmsdcc_host *host, char *buffer, | |
568 | unsigned int remain, u32 status) | |
569 | { | |
570 | void __iomem *base = host->base; | |
571 | char *ptr = buffer; | |
572 | ||
573 | do { | |
574 | unsigned int count, maxcnt; | |
575 | ||
576 | maxcnt = status & MCI_TXFIFOEMPTY ? MCI_FIFOSIZE : | |
577 | MCI_FIFOHALFSIZE; | |
578 | count = min(remain, maxcnt); | |
579 | ||
580 | writesl(base + MMCIFIFO, ptr, count >> 2); | |
581 | ptr += count; | |
582 | remain -= count; | |
583 | ||
584 | if (remain == 0) | |
585 | break; | |
586 | ||
8b1c2ba2 | 587 | status = msmsdcc_readl(host, MMCISTATUS); |
9d2bd738 SM |
588 | } while (status & MCI_TXFIFOHALFEMPTY); |
589 | ||
590 | return ptr - buffer; | |
591 | } | |
592 | ||
593 | static int | |
594 | msmsdcc_spin_on_status(struct msmsdcc_host *host, uint32_t mask, int maxspin) | |
595 | { | |
596 | while (maxspin) { | |
8b1c2ba2 | 597 | if ((msmsdcc_readl(host, MMCISTATUS) & mask)) |
9d2bd738 SM |
598 | return 0; |
599 | udelay(1); | |
600 | --maxspin; | |
601 | } | |
602 | return -ETIMEDOUT; | |
603 | } | |
604 | ||
605 | static int | |
606 | msmsdcc_pio_irq(int irq, void *dev_id) | |
607 | { | |
608 | struct msmsdcc_host *host = dev_id; | |
9d2bd738 SM |
609 | uint32_t status; |
610 | ||
8b1c2ba2 | 611 | status = msmsdcc_readl(host, MMCISTATUS); |
9d2bd738 SM |
612 | |
613 | do { | |
614 | unsigned long flags; | |
615 | unsigned int remain, len; | |
616 | char *buffer; | |
617 | ||
618 | if (!(status & (MCI_TXFIFOHALFEMPTY | MCI_RXDATAAVLBL))) { | |
619 | if (host->curr.xfer_remain == 0 || !msmsdcc_piopoll) | |
620 | break; | |
621 | ||
622 | if (msmsdcc_spin_on_status(host, | |
623 | (MCI_TXFIFOHALFEMPTY | | |
624 | MCI_RXDATAAVLBL), | |
625 | PIO_SPINMAX)) { | |
626 | break; | |
627 | } | |
628 | } | |
629 | ||
630 | /* Map the current scatter buffer */ | |
631 | local_irq_save(flags); | |
632 | buffer = kmap_atomic(sg_page(host->pio.sg), | |
633 | KM_BIO_SRC_IRQ) + host->pio.sg->offset; | |
634 | buffer += host->pio.sg_off; | |
635 | remain = host->pio.sg->length - host->pio.sg_off; | |
636 | len = 0; | |
637 | if (status & MCI_RXACTIVE) | |
638 | len = msmsdcc_pio_read(host, buffer, remain); | |
639 | if (status & MCI_TXACTIVE) | |
640 | len = msmsdcc_pio_write(host, buffer, remain, status); | |
641 | ||
642 | /* Unmap the buffer */ | |
643 | kunmap_atomic(buffer, KM_BIO_SRC_IRQ); | |
644 | local_irq_restore(flags); | |
645 | ||
646 | host->pio.sg_off += len; | |
647 | host->curr.xfer_remain -= len; | |
648 | host->curr.data_xfered += len; | |
649 | remain -= len; | |
650 | ||
651 | if (remain == 0) { | |
652 | /* This sg page is full - do some housekeeping */ | |
653 | if (status & MCI_RXACTIVE && host->curr.user_pages) | |
654 | flush_dcache_page(sg_page(host->pio.sg)); | |
655 | ||
656 | if (!--host->pio.sg_len) { | |
657 | memset(&host->pio, 0, sizeof(host->pio)); | |
658 | break; | |
659 | } | |
660 | ||
661 | /* Advance to next sg */ | |
662 | host->pio.sg++; | |
663 | host->pio.sg_off = 0; | |
664 | } | |
665 | ||
8b1c2ba2 | 666 | status = msmsdcc_readl(host, MMCISTATUS); |
9d2bd738 SM |
667 | } while (1); |
668 | ||
669 | if (status & MCI_RXACTIVE && host->curr.xfer_remain < MCI_FIFOSIZE) | |
8b1c2ba2 | 670 | msmsdcc_writel(host, MCI_RXDATAAVLBLMASK, MMCIMASK1); |
9d2bd738 SM |
671 | |
672 | if (!host->curr.xfer_remain) | |
8b1c2ba2 | 673 | msmsdcc_writel(host, 0, MMCIMASK1); |
9d2bd738 SM |
674 | |
675 | return IRQ_HANDLED; | |
676 | } | |
677 | ||
678 | static void msmsdcc_do_cmdirq(struct msmsdcc_host *host, uint32_t status) | |
679 | { | |
680 | struct mmc_command *cmd = host->curr.cmd; | |
9d2bd738 SM |
681 | |
682 | host->curr.cmd = NULL; | |
8b1c2ba2 SM |
683 | cmd->resp[0] = msmsdcc_readl(host, MMCIRESPONSE0); |
684 | cmd->resp[1] = msmsdcc_readl(host, MMCIRESPONSE1); | |
685 | cmd->resp[2] = msmsdcc_readl(host, MMCIRESPONSE2); | |
686 | cmd->resp[3] = msmsdcc_readl(host, MMCIRESPONSE3); | |
9d2bd738 | 687 | |
9d2bd738 SM |
688 | if (status & MCI_CMDTIMEOUT) { |
689 | cmd->error = -ETIMEDOUT; | |
690 | } else if (status & MCI_CMDCRCFAIL && | |
691 | cmd->flags & MMC_RSP_CRC) { | |
0a7ff7c7 | 692 | pr_err("%s: Command CRC error\n", mmc_hostname(host->mmc)); |
9d2bd738 SM |
693 | cmd->error = -EILSEQ; |
694 | } | |
695 | ||
696 | if (!cmd->data || cmd->error) { | |
697 | if (host->curr.data && host->dma.sg) | |
698 | msm_dmov_stop_cmd(host->dma.channel, | |
699 | &host->dma.hdr, 0); | |
700 | else if (host->curr.data) { /* Non DMA */ | |
701 | msmsdcc_stop_data(host); | |
702 | msmsdcc_request_end(host, cmd->mrq); | |
703 | } else /* host->data == NULL */ | |
704 | msmsdcc_request_end(host, cmd->mrq); | |
56a8b5b8 SM |
705 | } else if (cmd->data) |
706 | if (!(cmd->data->flags & MMC_DATA_READ)) | |
707 | msmsdcc_start_data(host, cmd->data, | |
708 | NULL, 0); | |
9d2bd738 SM |
709 | } |
710 | ||
b5a74d60 JP |
711 | static void |
712 | msmsdcc_handle_irq_data(struct msmsdcc_host *host, u32 status, | |
713 | void __iomem *base) | |
714 | { | |
56a8b5b8 | 715 | struct mmc_data *data; |
b5a74d60 | 716 | |
56a8b5b8 SM |
717 | if (status & (MCI_CMDSENT | MCI_CMDRESPEND | MCI_CMDCRCFAIL | |
718 | MCI_CMDTIMEOUT) && host->curr.cmd) { | |
719 | msmsdcc_do_cmdirq(host, status); | |
720 | } | |
721 | ||
722 | data = host->curr.data; | |
b5a74d60 JP |
723 | if (!data) |
724 | return; | |
725 | ||
726 | /* Check for data errors */ | |
727 | if (status & (MCI_DATACRCFAIL | MCI_DATATIMEOUT | | |
728 | MCI_TXUNDERRUN | MCI_RXOVERRUN)) { | |
729 | msmsdcc_data_err(host, data, status); | |
730 | host->curr.data_xfered = 0; | |
731 | if (host->dma.sg) | |
732 | msm_dmov_stop_cmd(host->dma.channel, | |
733 | &host->dma.hdr, 0); | |
734 | else { | |
735 | msmsdcc_stop_data(host); | |
736 | if (!data->stop) | |
737 | msmsdcc_request_end(host, data->mrq); | |
738 | else | |
739 | msmsdcc_start_command(host, data->stop, 0); | |
740 | } | |
741 | } | |
742 | ||
743 | /* Check for data done */ | |
744 | if (!host->curr.got_dataend && (status & MCI_DATAEND)) | |
745 | host->curr.got_dataend = 1; | |
746 | ||
747 | if (!host->curr.got_datablkend && (status & MCI_DATABLOCKEND)) | |
748 | host->curr.got_datablkend = 1; | |
749 | ||
750 | /* | |
751 | * If DMA is still in progress, we complete via the completion handler | |
752 | */ | |
753 | if (host->curr.got_dataend && host->curr.got_datablkend && | |
754 | !host->dma.busy) { | |
755 | /* | |
756 | * There appears to be an issue in the controller where | |
757 | * if you request a small block transfer (< fifo size), | |
758 | * you may get your DATAEND/DATABLKEND irq without the | |
759 | * PIO data irq. | |
760 | * | |
761 | * Check to see if there is still data to be read, | |
762 | * and simulate a PIO irq. | |
763 | */ | |
764 | if (readl(base + MMCISTATUS) & MCI_RXDATAAVLBL) | |
765 | msmsdcc_pio_irq(1, host); | |
766 | ||
767 | msmsdcc_stop_data(host); | |
768 | if (!data->error) | |
769 | host->curr.data_xfered = host->curr.xfer_size; | |
770 | ||
771 | if (!data->stop) | |
772 | msmsdcc_request_end(host, data->mrq); | |
773 | else | |
774 | msmsdcc_start_command(host, data->stop, 0); | |
775 | } | |
776 | } | |
777 | ||
9d2bd738 SM |
778 | static irqreturn_t |
779 | msmsdcc_irq(int irq, void *dev_id) | |
780 | { | |
781 | struct msmsdcc_host *host = dev_id; | |
782 | void __iomem *base = host->base; | |
783 | u32 status; | |
784 | int ret = 0; | |
785 | int cardint = 0; | |
786 | ||
787 | spin_lock(&host->lock); | |
788 | ||
789 | do { | |
8b1c2ba2 SM |
790 | struct mmc_data *data; |
791 | status = msmsdcc_readl(host, MMCISTATUS); | |
792 | status &= (msmsdcc_readl(host, MMCIMASK0) | | |
793 | MCI_DATABLOCKENDMASK); | |
794 | msmsdcc_writel(host, status, MMCICLEAR); | |
9d2bd738 | 795 | |
865c8064 SM |
796 | if (status & MCI_SDIOINTR) |
797 | status &= ~MCI_SDIOINTR; | |
798 | ||
799 | if (!status) | |
800 | break; | |
801 | ||
b5a74d60 | 802 | msmsdcc_handle_irq_data(host, status, base); |
9d2bd738 | 803 | |
9d2bd738 SM |
804 | if (status & MCI_SDIOINTOPER) { |
805 | cardint = 1; | |
806 | status &= ~MCI_SDIOINTOPER; | |
807 | } | |
808 | ret = 1; | |
809 | } while (status); | |
810 | ||
811 | spin_unlock(&host->lock); | |
812 | ||
813 | /* | |
814 | * We have to delay handling the card interrupt as it calls | |
815 | * back into the driver. | |
816 | */ | |
817 | if (cardint) | |
818 | mmc_signal_sdio_irq(host->mmc); | |
819 | ||
820 | return IRQ_RETVAL(ret); | |
821 | } | |
822 | ||
823 | static void | |
824 | msmsdcc_request(struct mmc_host *mmc, struct mmc_request *mrq) | |
825 | { | |
826 | struct msmsdcc_host *host = mmc_priv(mmc); | |
827 | unsigned long flags; | |
828 | ||
829 | WARN_ON(host->curr.mrq != NULL); | |
830 | WARN_ON(host->pwr == 0); | |
831 | ||
832 | spin_lock_irqsave(&host->lock, flags); | |
833 | ||
834 | host->stats.reqs++; | |
835 | ||
836 | if (host->eject) { | |
837 | if (mrq->data && !(mrq->data->flags & MMC_DATA_READ)) { | |
838 | mrq->cmd->error = 0; | |
839 | mrq->data->bytes_xfered = mrq->data->blksz * | |
840 | mrq->data->blocks; | |
841 | } else | |
842 | mrq->cmd->error = -ENOMEDIUM; | |
843 | ||
844 | spin_unlock_irqrestore(&host->lock, flags); | |
845 | mmc_request_done(mmc, mrq); | |
846 | return; | |
847 | } | |
848 | ||
849 | host->curr.mrq = mrq; | |
c7fc9370 SM |
850 | |
851 | /* Need to drop the host lock here in case | |
852 | * the busclk wd fires | |
853 | */ | |
854 | spin_unlock_irqrestore(&host->lock, flags); | |
865c8064 | 855 | if (!host->clks_on) |
c7fc9370 SM |
856 | msmsdcc_enable_clocks(host); |
857 | spin_lock_irqsave(&host->lock, flags); | |
9d2bd738 SM |
858 | |
859 | if (mrq->data && mrq->data->flags & MMC_DATA_READ) | |
56a8b5b8 SM |
860 | /* Queue/read data, daisy-chain command when data starts */ |
861 | msmsdcc_start_data(host, mrq->data, mrq->cmd, 0); | |
862 | else | |
863 | msmsdcc_start_command(host, mrq->cmd, 0); | |
9d2bd738 SM |
864 | |
865 | if (host->cmdpoll && !msmsdcc_spin_on_status(host, | |
866 | MCI_CMDRESPEND|MCI_CMDCRCFAIL|MCI_CMDTIMEOUT, | |
867 | CMD_SPINMAX)) { | |
8b1c2ba2 | 868 | uint32_t status = msmsdcc_readl(host, MMCISTATUS); |
9d2bd738 | 869 | msmsdcc_do_cmdirq(host, status); |
8b1c2ba2 SM |
870 | msmsdcc_writel(host, |
871 | MCI_CMDRESPEND | MCI_CMDCRCFAIL | MCI_CMDTIMEOUT, | |
872 | MMCICLEAR); | |
9d2bd738 SM |
873 | host->stats.cmdpoll_hits++; |
874 | } else { | |
875 | host->stats.cmdpoll_misses++; | |
9d2bd738 SM |
876 | } |
877 | spin_unlock_irqrestore(&host->lock, flags); | |
878 | } | |
879 | ||
880 | static void | |
881 | msmsdcc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) | |
882 | { | |
883 | struct msmsdcc_host *host = mmc_priv(mmc); | |
884 | u32 clk = 0, pwr = 0; | |
885 | int rc; | |
4adbbcc7 | 886 | unsigned long flags; |
9d2bd738 | 887 | |
865c8064 | 888 | if (!host->clks_on) |
c7fc9370 SM |
889 | msmsdcc_enable_clocks(host); |
890 | ||
891 | spin_lock_irqsave(&host->lock, flags); | |
9d2bd738 | 892 | |
865c8064 | 893 | if (ios->clock) { |
9d2bd738 SM |
894 | if (ios->clock != host->clk_rate) { |
895 | rc = clk_set_rate(host->clk, ios->clock); | |
896 | if (rc < 0) | |
0a7ff7c7 JP |
897 | pr_err("%s: Error setting clock rate (%d)\n", |
898 | mmc_hostname(host->mmc), rc); | |
9d2bd738 SM |
899 | else |
900 | host->clk_rate = ios->clock; | |
901 | } | |
902 | clk |= MCI_CLK_ENABLE; | |
903 | } | |
904 | ||
905 | if (ios->bus_width == MMC_BUS_WIDTH_4) | |
906 | clk |= (2 << 10); /* Set WIDEBUS */ | |
907 | ||
908 | if (ios->clock > 400000 && msmsdcc_pwrsave) | |
909 | clk |= (1 << 9); /* PWRSAVE */ | |
910 | ||
911 | clk |= (1 << 12); /* FLOW_ENA */ | |
912 | clk |= (1 << 15); /* feedback clock */ | |
913 | ||
914 | if (host->plat->translate_vdd) | |
915 | pwr |= host->plat->translate_vdd(mmc_dev(mmc), ios->vdd); | |
916 | ||
917 | switch (ios->power_mode) { | |
918 | case MMC_POWER_OFF: | |
9d2bd738 SM |
919 | break; |
920 | case MMC_POWER_UP: | |
921 | pwr |= MCI_PWR_UP; | |
922 | break; | |
923 | case MMC_POWER_ON: | |
9d2bd738 SM |
924 | pwr |= MCI_PWR_ON; |
925 | break; | |
926 | } | |
927 | ||
928 | if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN) | |
929 | pwr |= MCI_OD; | |
930 | ||
8b1c2ba2 | 931 | msmsdcc_writel(host, clk, MMCICLOCK); |
9d2bd738 SM |
932 | |
933 | if (host->pwr != pwr) { | |
934 | host->pwr = pwr; | |
8b1c2ba2 | 935 | msmsdcc_writel(host, pwr, MMCIPOWER); |
9d2bd738 | 936 | } |
c7fc9370 | 937 | msmsdcc_disable_clocks(host, 1); |
4adbbcc7 | 938 | spin_unlock_irqrestore(&host->lock, flags); |
9d2bd738 SM |
939 | } |
940 | ||
941 | static void msmsdcc_enable_sdio_irq(struct mmc_host *mmc, int enable) | |
942 | { | |
943 | struct msmsdcc_host *host = mmc_priv(mmc); | |
944 | unsigned long flags; | |
945 | u32 status; | |
946 | ||
947 | spin_lock_irqsave(&host->lock, flags); | |
948 | if (msmsdcc_sdioirq == 1) { | |
8b1c2ba2 | 949 | status = msmsdcc_readl(host, MMCIMASK0); |
9d2bd738 SM |
950 | if (enable) |
951 | status |= MCI_SDIOINTOPERMASK; | |
952 | else | |
953 | status &= ~MCI_SDIOINTOPERMASK; | |
954 | host->saved_irq0mask = status; | |
8b1c2ba2 | 955 | msmsdcc_writel(host, status, MMCIMASK0); |
9d2bd738 SM |
956 | } |
957 | spin_unlock_irqrestore(&host->lock, flags); | |
958 | } | |
959 | ||
960 | static const struct mmc_host_ops msmsdcc_ops = { | |
961 | .request = msmsdcc_request, | |
962 | .set_ios = msmsdcc_set_ios, | |
963 | .enable_sdio_irq = msmsdcc_enable_sdio_irq, | |
964 | }; | |
965 | ||
966 | static void | |
967 | msmsdcc_check_status(unsigned long data) | |
968 | { | |
969 | struct msmsdcc_host *host = (struct msmsdcc_host *)data; | |
970 | unsigned int status; | |
971 | ||
972 | if (!host->plat->status) { | |
973 | mmc_detect_change(host->mmc, 0); | |
974 | goto out; | |
975 | } | |
976 | ||
977 | status = host->plat->status(mmc_dev(host->mmc)); | |
978 | host->eject = !status; | |
979 | if (status ^ host->oldstat) { | |
0a7ff7c7 JP |
980 | pr_info("%s: Slot status change detected (%d -> %d)\n", |
981 | mmc_hostname(host->mmc), host->oldstat, status); | |
9d2bd738 SM |
982 | if (status) |
983 | mmc_detect_change(host->mmc, (5 * HZ) / 2); | |
984 | else | |
985 | mmc_detect_change(host->mmc, 0); | |
986 | } | |
987 | ||
988 | host->oldstat = status; | |
989 | ||
990 | out: | |
991 | if (host->timer.function) | |
992 | mod_timer(&host->timer, jiffies + HZ); | |
993 | } | |
994 | ||
995 | static irqreturn_t | |
996 | msmsdcc_platform_status_irq(int irq, void *dev_id) | |
997 | { | |
998 | struct msmsdcc_host *host = dev_id; | |
999 | ||
1000 | printk(KERN_DEBUG "%s: %d\n", __func__, irq); | |
1001 | msmsdcc_check_status((unsigned long) host); | |
1002 | return IRQ_HANDLED; | |
1003 | } | |
1004 | ||
1005 | static void | |
1006 | msmsdcc_status_notify_cb(int card_present, void *dev_id) | |
1007 | { | |
1008 | struct msmsdcc_host *host = dev_id; | |
1009 | ||
1010 | printk(KERN_DEBUG "%s: card_present %d\n", mmc_hostname(host->mmc), | |
1011 | card_present); | |
1012 | msmsdcc_check_status((unsigned long) host); | |
1013 | } | |
1014 | ||
865c8064 SM |
1015 | static void |
1016 | msmsdcc_busclk_expired(unsigned long _data) | |
1017 | { | |
1018 | struct msmsdcc_host *host = (struct msmsdcc_host *) _data; | |
1019 | unsigned long flags; | |
1020 | ||
1021 | spin_lock_irqsave(&host->lock, flags); | |
c7fc9370 | 1022 | dev_info(mmc_dev(host->mmc), "Bus clock timer expired\n"); |
865c8064 | 1023 | if (host->clks_on) |
c7fc9370 | 1024 | msmsdcc_disable_clocks(host, 0); |
865c8064 SM |
1025 | spin_unlock_irqrestore(&host->lock, flags); |
1026 | } | |
1027 | ||
9d2bd738 SM |
1028 | static int |
1029 | msmsdcc_init_dma(struct msmsdcc_host *host) | |
1030 | { | |
1031 | memset(&host->dma, 0, sizeof(struct msmsdcc_dma_data)); | |
1032 | host->dma.host = host; | |
1033 | host->dma.channel = -1; | |
1034 | ||
1035 | if (!host->dmares) | |
1036 | return -ENODEV; | |
1037 | ||
1038 | host->dma.nc = dma_alloc_coherent(NULL, | |
1039 | sizeof(struct msmsdcc_nc_dmadata), | |
1040 | &host->dma.nc_busaddr, | |
1041 | GFP_KERNEL); | |
1042 | if (host->dma.nc == NULL) { | |
0a7ff7c7 | 1043 | pr_err("Unable to allocate DMA buffer\n"); |
9d2bd738 SM |
1044 | return -ENOMEM; |
1045 | } | |
1046 | memset(host->dma.nc, 0x00, sizeof(struct msmsdcc_nc_dmadata)); | |
1047 | host->dma.cmd_busaddr = host->dma.nc_busaddr; | |
1048 | host->dma.cmdptr_busaddr = host->dma.nc_busaddr + | |
1049 | offsetof(struct msmsdcc_nc_dmadata, cmdptr); | |
1050 | host->dma.channel = host->dmares->start; | |
1051 | ||
1052 | return 0; | |
1053 | } | |
1054 | ||
1055 | #ifdef CONFIG_MMC_MSM7X00A_RESUME_IN_WQ | |
1056 | static void | |
1057 | do_resume_work(struct work_struct *work) | |
1058 | { | |
1059 | struct msmsdcc_host *host = | |
1060 | container_of(work, struct msmsdcc_host, resume_task); | |
1061 | struct mmc_host *mmc = host->mmc; | |
1062 | ||
1063 | if (mmc) { | |
1064 | mmc_resume_host(mmc); | |
1065 | if (host->stat_irq) | |
1066 | enable_irq(host->stat_irq); | |
1067 | } | |
1068 | } | |
1069 | #endif | |
1070 | ||
1071 | static int | |
1072 | msmsdcc_probe(struct platform_device *pdev) | |
1073 | { | |
1074 | struct mmc_platform_data *plat = pdev->dev.platform_data; | |
1075 | struct msmsdcc_host *host; | |
1076 | struct mmc_host *mmc; | |
1077 | struct resource *cmd_irqres = NULL; | |
1078 | struct resource *pio_irqres = NULL; | |
1079 | struct resource *stat_irqres = NULL; | |
1080 | struct resource *memres = NULL; | |
1081 | struct resource *dmares = NULL; | |
1082 | int ret; | |
1083 | ||
1084 | /* must have platform data */ | |
1085 | if (!plat) { | |
0a7ff7c7 | 1086 | pr_err("%s: Platform data not available\n", __func__); |
9d2bd738 SM |
1087 | ret = -EINVAL; |
1088 | goto out; | |
1089 | } | |
1090 | ||
1091 | if (pdev->id < 1 || pdev->id > 4) | |
1092 | return -EINVAL; | |
1093 | ||
1094 | if (pdev->resource == NULL || pdev->num_resources < 2) { | |
0a7ff7c7 | 1095 | pr_err("%s: Invalid resource\n", __func__); |
9d2bd738 SM |
1096 | return -ENXIO; |
1097 | } | |
1098 | ||
1099 | memres = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
1100 | dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0); | |
1101 | cmd_irqres = platform_get_resource_byname(pdev, IORESOURCE_IRQ, | |
1102 | "cmd_irq"); | |
1103 | pio_irqres = platform_get_resource_byname(pdev, IORESOURCE_IRQ, | |
1104 | "pio_irq"); | |
1105 | stat_irqres = platform_get_resource_byname(pdev, IORESOURCE_IRQ, | |
1106 | "status_irq"); | |
1107 | ||
1108 | if (!cmd_irqres || !pio_irqres || !memres) { | |
0a7ff7c7 | 1109 | pr_err("%s: Invalid resource\n", __func__); |
9d2bd738 SM |
1110 | return -ENXIO; |
1111 | } | |
1112 | ||
1113 | /* | |
1114 | * Setup our host structure | |
1115 | */ | |
1116 | ||
1117 | mmc = mmc_alloc_host(sizeof(struct msmsdcc_host), &pdev->dev); | |
1118 | if (!mmc) { | |
1119 | ret = -ENOMEM; | |
1120 | goto out; | |
1121 | } | |
1122 | ||
1123 | host = mmc_priv(mmc); | |
1124 | host->pdev_id = pdev->id; | |
1125 | host->plat = plat; | |
1126 | host->mmc = mmc; | |
56a8b5b8 | 1127 | host->curr.cmd = NULL; |
9d2bd738 SM |
1128 | |
1129 | host->cmdpoll = 1; | |
1130 | ||
1131 | host->base = ioremap(memres->start, PAGE_SIZE); | |
1132 | if (!host->base) { | |
1133 | ret = -ENOMEM; | |
1134 | goto out; | |
1135 | } | |
1136 | ||
1137 | host->cmd_irqres = cmd_irqres; | |
1138 | host->pio_irqres = pio_irqres; | |
1139 | host->memres = memres; | |
1140 | host->dmares = dmares; | |
1141 | spin_lock_init(&host->lock); | |
1142 | ||
1143 | /* | |
1144 | * Setup DMA | |
1145 | */ | |
1146 | msmsdcc_init_dma(host); | |
1147 | ||
4adbbcc7 | 1148 | /* Get our clocks */ |
9d2bd738 SM |
1149 | host->pclk = clk_get(&pdev->dev, "sdc_pclk"); |
1150 | if (IS_ERR(host->pclk)) { | |
1151 | ret = PTR_ERR(host->pclk); | |
1152 | goto host_free; | |
1153 | } | |
1154 | ||
9d2bd738 SM |
1155 | host->clk = clk_get(&pdev->dev, "sdc_clk"); |
1156 | if (IS_ERR(host->clk)) { | |
1157 | ret = PTR_ERR(host->clk); | |
4adbbcc7 | 1158 | goto pclk_put; |
9d2bd738 SM |
1159 | } |
1160 | ||
4adbbcc7 | 1161 | /* Enable clocks */ |
c7fc9370 | 1162 | ret = msmsdcc_enable_clocks(host); |
9d2bd738 SM |
1163 | if (ret) |
1164 | goto clk_put; | |
1165 | ||
1166 | ret = clk_set_rate(host->clk, msmsdcc_fmin); | |
1167 | if (ret) { | |
0a7ff7c7 | 1168 | pr_err("%s: Clock rate set failed (%d)\n", __func__, ret); |
9d2bd738 SM |
1169 | goto clk_disable; |
1170 | } | |
1171 | ||
4adbbcc7 | 1172 | host->pclk_rate = clk_get_rate(host->pclk); |
9d2bd738 SM |
1173 | host->clk_rate = clk_get_rate(host->clk); |
1174 | ||
9d2bd738 SM |
1175 | /* |
1176 | * Setup MMC host structure | |
1177 | */ | |
1178 | mmc->ops = &msmsdcc_ops; | |
1179 | mmc->f_min = msmsdcc_fmin; | |
1180 | mmc->f_max = msmsdcc_fmax; | |
1181 | mmc->ocr_avail = plat->ocr_mask; | |
1182 | ||
1183 | if (msmsdcc_4bit) | |
1184 | mmc->caps |= MMC_CAP_4_BIT_DATA; | |
1185 | if (msmsdcc_sdioirq) | |
1186 | mmc->caps |= MMC_CAP_SDIO_IRQ; | |
1187 | mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED; | |
1188 | ||
1189 | mmc->max_phys_segs = NR_SG; | |
1190 | mmc->max_hw_segs = NR_SG; | |
1191 | mmc->max_blk_size = 4096; /* MCI_DATA_CTL BLOCKSIZE up to 4096 */ | |
1192 | mmc->max_blk_count = 65536; | |
1193 | ||
1194 | mmc->max_req_size = 33554432; /* MCI_DATA_LENGTH is 25 bits */ | |
1195 | mmc->max_seg_size = mmc->max_req_size; | |
1196 | ||
8b1c2ba2 SM |
1197 | msmsdcc_writel(host, 0, MMCIMASK0); |
1198 | msmsdcc_writel(host, 0x5e007ff, MMCICLEAR); | |
9d2bd738 | 1199 | |
8b1c2ba2 | 1200 | msmsdcc_writel(host, MCI_IRQENABLE, MMCIMASK0); |
9d2bd738 SM |
1201 | host->saved_irq0mask = MCI_IRQENABLE; |
1202 | ||
1203 | /* | |
1204 | * Setup card detect change | |
1205 | */ | |
1206 | ||
1207 | memset(&host->timer, 0, sizeof(host->timer)); | |
1208 | ||
1209 | if (stat_irqres && !(stat_irqres->flags & IORESOURCE_DISABLED)) { | |
1210 | unsigned long irqflags = IRQF_SHARED | | |
1211 | (stat_irqres->flags & IRQF_TRIGGER_MASK); | |
1212 | ||
1213 | host->stat_irq = stat_irqres->start; | |
1214 | ret = request_irq(host->stat_irq, | |
1215 | msmsdcc_platform_status_irq, | |
1216 | irqflags, | |
1217 | DRIVER_NAME " (slot)", | |
1218 | host); | |
1219 | if (ret) { | |
0a7ff7c7 JP |
1220 | pr_err("%s: Unable to get slot IRQ %d (%d)\n", |
1221 | mmc_hostname(mmc), host->stat_irq, ret); | |
9d2bd738 SM |
1222 | goto clk_disable; |
1223 | } | |
1224 | } else if (plat->register_status_notify) { | |
1225 | plat->register_status_notify(msmsdcc_status_notify_cb, host); | |
1226 | } else if (!plat->status) | |
0a7ff7c7 | 1227 | pr_err("%s: No card detect facilities available\n", |
9d2bd738 SM |
1228 | mmc_hostname(mmc)); |
1229 | else { | |
1230 | init_timer(&host->timer); | |
1231 | host->timer.data = (unsigned long)host; | |
1232 | host->timer.function = msmsdcc_check_status; | |
1233 | host->timer.expires = jiffies + HZ; | |
1234 | add_timer(&host->timer); | |
1235 | } | |
1236 | ||
1237 | if (plat->status) { | |
1238 | host->oldstat = host->plat->status(mmc_dev(host->mmc)); | |
1239 | host->eject = !host->oldstat; | |
1240 | } | |
1241 | ||
865c8064 SM |
1242 | init_timer(&host->busclk_timer); |
1243 | host->busclk_timer.data = (unsigned long) host; | |
1244 | host->busclk_timer.function = msmsdcc_busclk_expired; | |
1245 | ||
9d2bd738 SM |
1246 | ret = request_irq(cmd_irqres->start, msmsdcc_irq, IRQF_SHARED, |
1247 | DRIVER_NAME " (cmd)", host); | |
1248 | if (ret) | |
1249 | goto stat_irq_free; | |
1250 | ||
1251 | ret = request_irq(pio_irqres->start, msmsdcc_pio_irq, IRQF_SHARED, | |
1252 | DRIVER_NAME " (pio)", host); | |
1253 | if (ret) | |
1254 | goto cmd_irq_free; | |
1255 | ||
1256 | mmc_set_drvdata(pdev, mmc); | |
1257 | mmc_add_host(mmc); | |
1258 | ||
0a7ff7c7 JP |
1259 | pr_info("%s: Qualcomm MSM SDCC at 0x%016llx irq %d,%d dma %d\n", |
1260 | mmc_hostname(mmc), (unsigned long long)memres->start, | |
1261 | (unsigned int) cmd_irqres->start, | |
1262 | (unsigned int) host->stat_irq, host->dma.channel); | |
1263 | pr_info("%s: 4 bit data mode %s\n", mmc_hostname(mmc), | |
1264 | (mmc->caps & MMC_CAP_4_BIT_DATA ? "enabled" : "disabled")); | |
1265 | pr_info("%s: MMC clock %u -> %u Hz, PCLK %u Hz\n", | |
1266 | mmc_hostname(mmc), msmsdcc_fmin, msmsdcc_fmax, host->pclk_rate); | |
1267 | pr_info("%s: Slot eject status = %d\n", mmc_hostname(mmc), host->eject); | |
1268 | pr_info("%s: Power save feature enable = %d\n", | |
1269 | mmc_hostname(mmc), msmsdcc_pwrsave); | |
9d2bd738 SM |
1270 | |
1271 | if (host->dma.channel != -1) { | |
0a7ff7c7 JP |
1272 | pr_info("%s: DM non-cached buffer at %p, dma_addr 0x%.8x\n", |
1273 | mmc_hostname(mmc), host->dma.nc, host->dma.nc_busaddr); | |
1274 | pr_info("%s: DM cmd busaddr 0x%.8x, cmdptr busaddr 0x%.8x\n", | |
1275 | mmc_hostname(mmc), host->dma.cmd_busaddr, | |
1276 | host->dma.cmdptr_busaddr); | |
9d2bd738 | 1277 | } else |
0a7ff7c7 | 1278 | pr_info("%s: PIO transfer enabled\n", mmc_hostname(mmc)); |
9d2bd738 | 1279 | if (host->timer.function) |
0a7ff7c7 | 1280 | pr_info("%s: Polling status mode enabled\n", mmc_hostname(mmc)); |
9d2bd738 | 1281 | |
c7fc9370 | 1282 | msmsdcc_disable_clocks(host, 1); |
9d2bd738 SM |
1283 | return 0; |
1284 | cmd_irq_free: | |
1285 | free_irq(cmd_irqres->start, host); | |
1286 | stat_irq_free: | |
1287 | if (host->stat_irq) | |
1288 | free_irq(host->stat_irq, host); | |
1289 | clk_disable: | |
c7fc9370 | 1290 | msmsdcc_disable_clocks(host, 0); |
9d2bd738 SM |
1291 | clk_put: |
1292 | clk_put(host->clk); | |
9d2bd738 SM |
1293 | pclk_put: |
1294 | clk_put(host->pclk); | |
1295 | host_free: | |
1296 | mmc_free_host(mmc); | |
1297 | out: | |
1298 | return ret; | |
1299 | } | |
1300 | ||
1301 | static int | |
1302 | msmsdcc_suspend(struct platform_device *dev, pm_message_t state) | |
1303 | { | |
1304 | struct mmc_host *mmc = mmc_get_drvdata(dev); | |
1305 | int rc = 0; | |
56a8b5b8 | 1306 | unsigned long flags; |
9d2bd738 SM |
1307 | |
1308 | if (mmc) { | |
1309 | struct msmsdcc_host *host = mmc_priv(mmc); | |
1310 | ||
56a8b5b8 | 1311 | spin_lock_irqsave(&host->lock, flags); |
9d2bd738 SM |
1312 | if (host->stat_irq) |
1313 | disable_irq(host->stat_irq); | |
1314 | ||
1315 | if (mmc->card && mmc->card->type != MMC_TYPE_SDIO) | |
1316 | rc = mmc_suspend_host(mmc, state); | |
1317 | if (!rc) { | |
8b1c2ba2 | 1318 | msmsdcc_writel(host, 0, MMCIMASK0); |
9d2bd738 | 1319 | |
9d2bd738 | 1320 | } |
56a8b5b8 | 1321 | spin_unlock_irqrestore(&host->lock, flags); |
c7fc9370 SM |
1322 | if (host->clks_on) |
1323 | msmsdcc_disable_clocks(host, 0); | |
9d2bd738 SM |
1324 | } |
1325 | return rc; | |
1326 | } | |
1327 | ||
1328 | static int | |
1329 | msmsdcc_resume(struct platform_device *dev) | |
1330 | { | |
1331 | struct mmc_host *mmc = mmc_get_drvdata(dev); | |
9d2bd738 SM |
1332 | |
1333 | if (mmc) { | |
1334 | struct msmsdcc_host *host = mmc_priv(mmc); | |
1335 | ||
c7fc9370 | 1336 | msmsdcc_enable_clocks(host); |
56a8b5b8 | 1337 | |
8b1c2ba2 | 1338 | msmsdcc_writel(host, host->saved_irq0mask, MMCIMASK0); |
9d2bd738 | 1339 | |
9d2bd738 SM |
1340 | if (mmc->card && mmc->card->type != MMC_TYPE_SDIO) |
1341 | mmc_resume_host(mmc); | |
5b8a2fb3 | 1342 | if (host->stat_irq) |
9d2bd738 | 1343 | enable_irq(host->stat_irq); |
c7fc9370 | 1344 | msmsdcc_disable_clocks(host, 1); |
9d2bd738 SM |
1345 | } |
1346 | return 0; | |
1347 | } | |
1348 | ||
1349 | static struct platform_driver msmsdcc_driver = { | |
1350 | .probe = msmsdcc_probe, | |
1351 | .suspend = msmsdcc_suspend, | |
1352 | .resume = msmsdcc_resume, | |
1353 | .driver = { | |
1354 | .name = "msm_sdcc", | |
1355 | }, | |
1356 | }; | |
1357 | ||
1358 | static int __init msmsdcc_init(void) | |
1359 | { | |
1360 | return platform_driver_register(&msmsdcc_driver); | |
1361 | } | |
1362 | ||
1363 | static void __exit msmsdcc_exit(void) | |
1364 | { | |
1365 | platform_driver_unregister(&msmsdcc_driver); | |
1366 | } | |
1367 | ||
1368 | module_init(msmsdcc_init); | |
1369 | module_exit(msmsdcc_exit); | |
1370 | ||
1371 | MODULE_DESCRIPTION("Qualcomm MSM 7X00A Multimedia Card Interface driver"); | |
1372 | MODULE_LICENSE("GPL"); |