| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1 | /* | 
|  | 2 | * Atmel MultiMedia Card Interface driver | 
|  | 3 | * | 
|  | 4 | * Copyright (C) 2004-2008 Atmel Corporation | 
|  | 5 | * | 
|  | 6 | * 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 | 
|  | 8 | * published by the Free Software Foundation. | 
|  | 9 | */ | 
|  | 10 | #include <linux/blkdev.h> | 
|  | 11 | #include <linux/clk.h> | 
| Haavard Skinnemoen | deec9ae | 2008-07-24 14:18:59 +0200 | [diff] [blame] | 12 | #include <linux/debugfs.h> | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 13 | #include <linux/device.h> | 
| Haavard Skinnemoen | 65e8b08 | 2008-07-30 20:29:03 +0200 | [diff] [blame] | 14 | #include <linux/dmaengine.h> | 
|  | 15 | #include <linux/dma-mapping.h> | 
| Ben Nizette | fbfca4b | 2008-07-18 16:48:09 +1000 | [diff] [blame] | 16 | #include <linux/err.h> | 
| David Brownell | 3c26e17 | 2008-07-27 02:34:45 -0700 | [diff] [blame] | 17 | #include <linux/gpio.h> | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 18 | #include <linux/init.h> | 
|  | 19 | #include <linux/interrupt.h> | 
|  | 20 | #include <linux/ioport.h> | 
|  | 21 | #include <linux/module.h> | 
|  | 22 | #include <linux/platform_device.h> | 
|  | 23 | #include <linux/scatterlist.h> | 
| Haavard Skinnemoen | deec9ae | 2008-07-24 14:18:59 +0200 | [diff] [blame] | 24 | #include <linux/seq_file.h> | 
|  | 25 | #include <linux/stat.h> | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 26 |  | 
|  | 27 | #include <linux/mmc/host.h> | 
| Nicolas Ferre | c42aa77 | 2008-11-20 15:59:12 +0100 | [diff] [blame] | 28 | #include <linux/atmel-mci.h> | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 29 |  | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 30 | #include <asm/io.h> | 
|  | 31 | #include <asm/unaligned.h> | 
|  | 32 |  | 
| Haavard Skinnemoen | 3663b73 | 2008-08-05 13:57:38 +0200 | [diff] [blame] | 33 | #include <mach/board.h> | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 34 |  | 
|  | 35 | #include "atmel-mci-regs.h" | 
|  | 36 |  | 
|  | 37 | #define ATMCI_DATA_ERROR_FLAGS	(MCI_DCRCE | MCI_DTOE | MCI_OVRE | MCI_UNRE) | 
| Haavard Skinnemoen | 65e8b08 | 2008-07-30 20:29:03 +0200 | [diff] [blame] | 38 | #define ATMCI_DMA_THRESHOLD	16 | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 39 |  | 
|  | 40 | enum { | 
|  | 41 | EVENT_CMD_COMPLETE = 0, | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 42 | EVENT_XFER_COMPLETE, | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 43 | EVENT_DATA_COMPLETE, | 
|  | 44 | EVENT_DATA_ERROR, | 
|  | 45 | }; | 
|  | 46 |  | 
|  | 47 | enum atmel_mci_state { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 48 | STATE_IDLE = 0, | 
|  | 49 | STATE_SENDING_CMD, | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 50 | STATE_SENDING_DATA, | 
|  | 51 | STATE_DATA_BUSY, | 
|  | 52 | STATE_SENDING_STOP, | 
|  | 53 | STATE_DATA_ERROR, | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 54 | }; | 
|  | 55 |  | 
| Haavard Skinnemoen | 65e8b08 | 2008-07-30 20:29:03 +0200 | [diff] [blame] | 56 | struct atmel_mci_dma { | 
|  | 57 | #ifdef CONFIG_MMC_ATMELMCI_DMA | 
| Haavard Skinnemoen | 65e8b08 | 2008-07-30 20:29:03 +0200 | [diff] [blame] | 58 | struct dma_chan			*chan; | 
|  | 59 | struct dma_async_tx_descriptor	*data_desc; | 
|  | 60 | #endif | 
|  | 61 | }; | 
|  | 62 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 63 | /** | 
|  | 64 | * struct atmel_mci - MMC controller state shared between all slots | 
|  | 65 | * @lock: Spinlock protecting the queue and associated data. | 
|  | 66 | * @regs: Pointer to MMIO registers. | 
|  | 67 | * @sg: Scatterlist entry currently being processed by PIO code, if any. | 
|  | 68 | * @pio_offset: Offset into the current scatterlist entry. | 
|  | 69 | * @cur_slot: The slot which is currently using the controller. | 
|  | 70 | * @mrq: The request currently being processed on @cur_slot, | 
|  | 71 | *	or NULL if the controller is idle. | 
|  | 72 | * @cmd: The command currently being sent to the card, or NULL. | 
|  | 73 | * @data: The data currently being transferred, or NULL if no data | 
|  | 74 | *	transfer is in progress. | 
| Haavard Skinnemoen | 65e8b08 | 2008-07-30 20:29:03 +0200 | [diff] [blame] | 75 | * @dma: DMA client state. | 
|  | 76 | * @data_chan: DMA channel being used for the current data transfer. | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 77 | * @cmd_status: Snapshot of SR taken upon completion of the current | 
|  | 78 | *	command. Only valid when EVENT_CMD_COMPLETE is pending. | 
|  | 79 | * @data_status: Snapshot of SR taken upon completion of the current | 
|  | 80 | *	data transfer. Only valid when EVENT_DATA_COMPLETE or | 
|  | 81 | *	EVENT_DATA_ERROR is pending. | 
|  | 82 | * @stop_cmdr: Value to be loaded into CMDR when the stop command is | 
|  | 83 | *	to be sent. | 
|  | 84 | * @tasklet: Tasklet running the request state machine. | 
|  | 85 | * @pending_events: Bitmask of events flagged by the interrupt handler | 
|  | 86 | *	to be processed by the tasklet. | 
|  | 87 | * @completed_events: Bitmask of events which the state machine has | 
|  | 88 | *	processed. | 
|  | 89 | * @state: Tasklet state. | 
|  | 90 | * @queue: List of slots waiting for access to the controller. | 
|  | 91 | * @need_clock_update: Update the clock rate before the next request. | 
|  | 92 | * @need_reset: Reset controller before next request. | 
|  | 93 | * @mode_reg: Value of the MR register. | 
|  | 94 | * @bus_hz: The rate of @mck in Hz. This forms the basis for MMC bus | 
|  | 95 | *	rate and timeout calculations. | 
|  | 96 | * @mapbase: Physical address of the MMIO registers. | 
|  | 97 | * @mck: The peripheral bus clock hooked up to the MMC controller. | 
|  | 98 | * @pdev: Platform device associated with the MMC controller. | 
|  | 99 | * @slot: Slots sharing this MMC controller. | 
|  | 100 | * | 
|  | 101 | * Locking | 
|  | 102 | * ======= | 
|  | 103 | * | 
|  | 104 | * @lock is a softirq-safe spinlock protecting @queue as well as | 
|  | 105 | * @cur_slot, @mrq and @state. These must always be updated | 
|  | 106 | * at the same time while holding @lock. | 
|  | 107 | * | 
|  | 108 | * @lock also protects mode_reg and need_clock_update since these are | 
|  | 109 | * used to synchronize mode register updates with the queue | 
|  | 110 | * processing. | 
|  | 111 | * | 
|  | 112 | * The @mrq field of struct atmel_mci_slot is also protected by @lock, | 
|  | 113 | * and must always be written at the same time as the slot is added to | 
|  | 114 | * @queue. | 
|  | 115 | * | 
|  | 116 | * @pending_events and @completed_events are accessed using atomic bit | 
|  | 117 | * operations, so they don't need any locking. | 
|  | 118 | * | 
|  | 119 | * None of the fields touched by the interrupt handler need any | 
|  | 120 | * locking. However, ordering is important: Before EVENT_DATA_ERROR or | 
|  | 121 | * EVENT_DATA_COMPLETE is set in @pending_events, all data-related | 
|  | 122 | * interrupts must be disabled and @data_status updated with a | 
|  | 123 | * snapshot of SR. Similarly, before EVENT_CMD_COMPLETE is set, the | 
|  | 124 | * CMDRDY interupt must be disabled and @cmd_status updated with a | 
|  | 125 | * snapshot of SR, and before EVENT_XFER_COMPLETE can be set, the | 
|  | 126 | * bytes_xfered field of @data must be written. This is ensured by | 
|  | 127 | * using barriers. | 
|  | 128 | */ | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 129 | struct atmel_mci { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 130 | spinlock_t		lock; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 131 | void __iomem		*regs; | 
|  | 132 |  | 
|  | 133 | struct scatterlist	*sg; | 
|  | 134 | unsigned int		pio_offset; | 
|  | 135 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 136 | struct atmel_mci_slot	*cur_slot; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 137 | struct mmc_request	*mrq; | 
|  | 138 | struct mmc_command	*cmd; | 
|  | 139 | struct mmc_data		*data; | 
|  | 140 |  | 
| Haavard Skinnemoen | 65e8b08 | 2008-07-30 20:29:03 +0200 | [diff] [blame] | 141 | struct atmel_mci_dma	dma; | 
|  | 142 | struct dma_chan		*data_chan; | 
|  | 143 |  | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 144 | u32			cmd_status; | 
|  | 145 | u32			data_status; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 146 | u32			stop_cmdr; | 
|  | 147 |  | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 148 | struct tasklet_struct	tasklet; | 
|  | 149 | unsigned long		pending_events; | 
|  | 150 | unsigned long		completed_events; | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 151 | enum atmel_mci_state	state; | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 152 | struct list_head	queue; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 153 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 154 | bool			need_clock_update; | 
|  | 155 | bool			need_reset; | 
|  | 156 | u32			mode_reg; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 157 | unsigned long		bus_hz; | 
|  | 158 | unsigned long		mapbase; | 
|  | 159 | struct clk		*mck; | 
|  | 160 | struct platform_device	*pdev; | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 161 |  | 
|  | 162 | struct atmel_mci_slot	*slot[ATMEL_MCI_MAX_NR_SLOTS]; | 
|  | 163 | }; | 
|  | 164 |  | 
|  | 165 | /** | 
|  | 166 | * struct atmel_mci_slot - MMC slot state | 
|  | 167 | * @mmc: The mmc_host representing this slot. | 
|  | 168 | * @host: The MMC controller this slot is using. | 
|  | 169 | * @sdc_reg: Value of SDCR to be written before using this slot. | 
|  | 170 | * @mrq: mmc_request currently being processed or waiting to be | 
|  | 171 | *	processed, or NULL when the slot is idle. | 
|  | 172 | * @queue_node: List node for placing this node in the @queue list of | 
|  | 173 | *	&struct atmel_mci. | 
|  | 174 | * @clock: Clock rate configured by set_ios(). Protected by host->lock. | 
|  | 175 | * @flags: Random state bits associated with the slot. | 
|  | 176 | * @detect_pin: GPIO pin used for card detection, or negative if not | 
|  | 177 | *	available. | 
|  | 178 | * @wp_pin: GPIO pin used for card write protect sending, or negative | 
|  | 179 | *	if not available. | 
| Jonas Larsson | 1c1452b | 2009-03-31 11:16:48 +0200 | [diff] [blame] | 180 | * @detect_is_active_high: The state of the detect pin when it is active. | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 181 | * @detect_timer: Timer used for debouncing @detect_pin interrupts. | 
|  | 182 | */ | 
|  | 183 | struct atmel_mci_slot { | 
|  | 184 | struct mmc_host		*mmc; | 
|  | 185 | struct atmel_mci	*host; | 
|  | 186 |  | 
|  | 187 | u32			sdc_reg; | 
|  | 188 |  | 
|  | 189 | struct mmc_request	*mrq; | 
|  | 190 | struct list_head	queue_node; | 
|  | 191 |  | 
|  | 192 | unsigned int		clock; | 
|  | 193 | unsigned long		flags; | 
|  | 194 | #define ATMCI_CARD_PRESENT	0 | 
|  | 195 | #define ATMCI_CARD_NEED_INIT	1 | 
|  | 196 | #define ATMCI_SHUTDOWN		2 | 
|  | 197 |  | 
|  | 198 | int			detect_pin; | 
|  | 199 | int			wp_pin; | 
| Jonas Larsson | 1c1452b | 2009-03-31 11:16:48 +0200 | [diff] [blame] | 200 | bool			detect_is_active_high; | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 201 |  | 
|  | 202 | struct timer_list	detect_timer; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 203 | }; | 
|  | 204 |  | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 205 | #define atmci_test_and_clear_pending(host, event)		\ | 
|  | 206 | test_and_clear_bit(event, &host->pending_events) | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 207 | #define atmci_set_completed(host, event)			\ | 
|  | 208 | set_bit(event, &host->completed_events) | 
|  | 209 | #define atmci_set_pending(host, event)				\ | 
|  | 210 | set_bit(event, &host->pending_events) | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 211 |  | 
| Haavard Skinnemoen | deec9ae | 2008-07-24 14:18:59 +0200 | [diff] [blame] | 212 | /* | 
|  | 213 | * The debugfs stuff below is mostly optimized away when | 
|  | 214 | * CONFIG_DEBUG_FS is not set. | 
|  | 215 | */ | 
|  | 216 | static int atmci_req_show(struct seq_file *s, void *v) | 
|  | 217 | { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 218 | struct atmel_mci_slot	*slot = s->private; | 
|  | 219 | struct mmc_request	*mrq; | 
| Haavard Skinnemoen | deec9ae | 2008-07-24 14:18:59 +0200 | [diff] [blame] | 220 | struct mmc_command	*cmd; | 
|  | 221 | struct mmc_command	*stop; | 
|  | 222 | struct mmc_data		*data; | 
|  | 223 |  | 
|  | 224 | /* Make sure we get a consistent snapshot */ | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 225 | spin_lock_bh(&slot->host->lock); | 
|  | 226 | mrq = slot->mrq; | 
| Haavard Skinnemoen | deec9ae | 2008-07-24 14:18:59 +0200 | [diff] [blame] | 227 |  | 
|  | 228 | if (mrq) { | 
|  | 229 | cmd = mrq->cmd; | 
|  | 230 | data = mrq->data; | 
|  | 231 | stop = mrq->stop; | 
|  | 232 |  | 
|  | 233 | if (cmd) | 
|  | 234 | seq_printf(s, | 
|  | 235 | "CMD%u(0x%x) flg %x rsp %x %x %x %x err %d\n", | 
|  | 236 | cmd->opcode, cmd->arg, cmd->flags, | 
|  | 237 | cmd->resp[0], cmd->resp[1], cmd->resp[2], | 
|  | 238 | cmd->resp[2], cmd->error); | 
|  | 239 | if (data) | 
|  | 240 | seq_printf(s, "DATA %u / %u * %u flg %x err %d\n", | 
|  | 241 | data->bytes_xfered, data->blocks, | 
|  | 242 | data->blksz, data->flags, data->error); | 
|  | 243 | if (stop) | 
|  | 244 | seq_printf(s, | 
|  | 245 | "CMD%u(0x%x) flg %x rsp %x %x %x %x err %d\n", | 
|  | 246 | stop->opcode, stop->arg, stop->flags, | 
|  | 247 | stop->resp[0], stop->resp[1], stop->resp[2], | 
|  | 248 | stop->resp[2], stop->error); | 
|  | 249 | } | 
|  | 250 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 251 | spin_unlock_bh(&slot->host->lock); | 
| Haavard Skinnemoen | deec9ae | 2008-07-24 14:18:59 +0200 | [diff] [blame] | 252 |  | 
|  | 253 | return 0; | 
|  | 254 | } | 
|  | 255 |  | 
|  | 256 | static int atmci_req_open(struct inode *inode, struct file *file) | 
|  | 257 | { | 
|  | 258 | return single_open(file, atmci_req_show, inode->i_private); | 
|  | 259 | } | 
|  | 260 |  | 
|  | 261 | static const struct file_operations atmci_req_fops = { | 
|  | 262 | .owner		= THIS_MODULE, | 
|  | 263 | .open		= atmci_req_open, | 
|  | 264 | .read		= seq_read, | 
|  | 265 | .llseek		= seq_lseek, | 
|  | 266 | .release	= single_release, | 
|  | 267 | }; | 
|  | 268 |  | 
|  | 269 | static void atmci_show_status_reg(struct seq_file *s, | 
|  | 270 | const char *regname, u32 value) | 
|  | 271 | { | 
|  | 272 | static const char	*sr_bit[] = { | 
|  | 273 | [0]	= "CMDRDY", | 
|  | 274 | [1]	= "RXRDY", | 
|  | 275 | [2]	= "TXRDY", | 
|  | 276 | [3]	= "BLKE", | 
|  | 277 | [4]	= "DTIP", | 
|  | 278 | [5]	= "NOTBUSY", | 
|  | 279 | [8]	= "SDIOIRQA", | 
|  | 280 | [9]	= "SDIOIRQB", | 
|  | 281 | [16]	= "RINDE", | 
|  | 282 | [17]	= "RDIRE", | 
|  | 283 | [18]	= "RCRCE", | 
|  | 284 | [19]	= "RENDE", | 
|  | 285 | [20]	= "RTOE", | 
|  | 286 | [21]	= "DCRCE", | 
|  | 287 | [22]	= "DTOE", | 
|  | 288 | [30]	= "OVRE", | 
|  | 289 | [31]	= "UNRE", | 
|  | 290 | }; | 
|  | 291 | unsigned int		i; | 
|  | 292 |  | 
|  | 293 | seq_printf(s, "%s:\t0x%08x", regname, value); | 
|  | 294 | for (i = 0; i < ARRAY_SIZE(sr_bit); i++) { | 
|  | 295 | if (value & (1 << i)) { | 
|  | 296 | if (sr_bit[i]) | 
|  | 297 | seq_printf(s, " %s", sr_bit[i]); | 
|  | 298 | else | 
|  | 299 | seq_puts(s, " UNKNOWN"); | 
|  | 300 | } | 
|  | 301 | } | 
|  | 302 | seq_putc(s, '\n'); | 
|  | 303 | } | 
|  | 304 |  | 
|  | 305 | static int atmci_regs_show(struct seq_file *s, void *v) | 
|  | 306 | { | 
|  | 307 | struct atmel_mci	*host = s->private; | 
|  | 308 | u32			*buf; | 
|  | 309 |  | 
|  | 310 | buf = kmalloc(MCI_REGS_SIZE, GFP_KERNEL); | 
|  | 311 | if (!buf) | 
|  | 312 | return -ENOMEM; | 
|  | 313 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 314 | /* | 
|  | 315 | * Grab a more or less consistent snapshot. Note that we're | 
|  | 316 | * not disabling interrupts, so IMR and SR may not be | 
|  | 317 | * consistent. | 
|  | 318 | */ | 
|  | 319 | spin_lock_bh(&host->lock); | 
| Haavard Skinnemoen | 87e60f2 | 2008-09-19 21:09:27 +0200 | [diff] [blame] | 320 | clk_enable(host->mck); | 
| Haavard Skinnemoen | deec9ae | 2008-07-24 14:18:59 +0200 | [diff] [blame] | 321 | memcpy_fromio(buf, host->regs, MCI_REGS_SIZE); | 
| Haavard Skinnemoen | 87e60f2 | 2008-09-19 21:09:27 +0200 | [diff] [blame] | 322 | clk_disable(host->mck); | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 323 | spin_unlock_bh(&host->lock); | 
| Haavard Skinnemoen | deec9ae | 2008-07-24 14:18:59 +0200 | [diff] [blame] | 324 |  | 
|  | 325 | seq_printf(s, "MR:\t0x%08x%s%s CLKDIV=%u\n", | 
|  | 326 | buf[MCI_MR / 4], | 
|  | 327 | buf[MCI_MR / 4] & MCI_MR_RDPROOF ? " RDPROOF" : "", | 
|  | 328 | buf[MCI_MR / 4] & MCI_MR_WRPROOF ? " WRPROOF" : "", | 
|  | 329 | buf[MCI_MR / 4] & 0xff); | 
|  | 330 | seq_printf(s, "DTOR:\t0x%08x\n", buf[MCI_DTOR / 4]); | 
|  | 331 | seq_printf(s, "SDCR:\t0x%08x\n", buf[MCI_SDCR / 4]); | 
|  | 332 | seq_printf(s, "ARGR:\t0x%08x\n", buf[MCI_ARGR / 4]); | 
|  | 333 | seq_printf(s, "BLKR:\t0x%08x BCNT=%u BLKLEN=%u\n", | 
|  | 334 | buf[MCI_BLKR / 4], | 
|  | 335 | buf[MCI_BLKR / 4] & 0xffff, | 
|  | 336 | (buf[MCI_BLKR / 4] >> 16) & 0xffff); | 
|  | 337 |  | 
|  | 338 | /* Don't read RSPR and RDR; it will consume the data there */ | 
|  | 339 |  | 
|  | 340 | atmci_show_status_reg(s, "SR", buf[MCI_SR / 4]); | 
|  | 341 | atmci_show_status_reg(s, "IMR", buf[MCI_IMR / 4]); | 
|  | 342 |  | 
| Haavard Skinnemoen | b17339a | 2008-09-19 21:09:28 +0200 | [diff] [blame] | 343 | kfree(buf); | 
|  | 344 |  | 
| Haavard Skinnemoen | deec9ae | 2008-07-24 14:18:59 +0200 | [diff] [blame] | 345 | return 0; | 
|  | 346 | } | 
|  | 347 |  | 
|  | 348 | static int atmci_regs_open(struct inode *inode, struct file *file) | 
|  | 349 | { | 
|  | 350 | return single_open(file, atmci_regs_show, inode->i_private); | 
|  | 351 | } | 
|  | 352 |  | 
|  | 353 | static const struct file_operations atmci_regs_fops = { | 
|  | 354 | .owner		= THIS_MODULE, | 
|  | 355 | .open		= atmci_regs_open, | 
|  | 356 | .read		= seq_read, | 
|  | 357 | .llseek		= seq_lseek, | 
|  | 358 | .release	= single_release, | 
|  | 359 | }; | 
|  | 360 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 361 | static void atmci_init_debugfs(struct atmel_mci_slot *slot) | 
| Haavard Skinnemoen | deec9ae | 2008-07-24 14:18:59 +0200 | [diff] [blame] | 362 | { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 363 | struct mmc_host		*mmc = slot->mmc; | 
|  | 364 | struct atmel_mci	*host = slot->host; | 
|  | 365 | struct dentry		*root; | 
|  | 366 | struct dentry		*node; | 
| Haavard Skinnemoen | deec9ae | 2008-07-24 14:18:59 +0200 | [diff] [blame] | 367 |  | 
| Haavard Skinnemoen | deec9ae | 2008-07-24 14:18:59 +0200 | [diff] [blame] | 368 | root = mmc->debugfs_root; | 
|  | 369 | if (!root) | 
|  | 370 | return; | 
|  | 371 |  | 
|  | 372 | node = debugfs_create_file("regs", S_IRUSR, root, host, | 
|  | 373 | &atmci_regs_fops); | 
|  | 374 | if (IS_ERR(node)) | 
|  | 375 | return; | 
|  | 376 | if (!node) | 
|  | 377 | goto err; | 
|  | 378 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 379 | node = debugfs_create_file("req", S_IRUSR, root, slot, &atmci_req_fops); | 
| Haavard Skinnemoen | deec9ae | 2008-07-24 14:18:59 +0200 | [diff] [blame] | 380 | if (!node) | 
|  | 381 | goto err; | 
|  | 382 |  | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 383 | node = debugfs_create_u32("state", S_IRUSR, root, (u32 *)&host->state); | 
|  | 384 | if (!node) | 
|  | 385 | goto err; | 
|  | 386 |  | 
| Haavard Skinnemoen | deec9ae | 2008-07-24 14:18:59 +0200 | [diff] [blame] | 387 | node = debugfs_create_x32("pending_events", S_IRUSR, root, | 
|  | 388 | (u32 *)&host->pending_events); | 
|  | 389 | if (!node) | 
|  | 390 | goto err; | 
|  | 391 |  | 
|  | 392 | node = debugfs_create_x32("completed_events", S_IRUSR, root, | 
|  | 393 | (u32 *)&host->completed_events); | 
|  | 394 | if (!node) | 
|  | 395 | goto err; | 
|  | 396 |  | 
|  | 397 | return; | 
|  | 398 |  | 
|  | 399 | err: | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 400 | dev_err(&mmc->class_dev, "failed to initialize debugfs for slot\n"); | 
| Haavard Skinnemoen | deec9ae | 2008-07-24 14:18:59 +0200 | [diff] [blame] | 401 | } | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 402 |  | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 403 | static inline unsigned int ns_to_clocks(struct atmel_mci *host, | 
|  | 404 | unsigned int ns) | 
|  | 405 | { | 
|  | 406 | return (ns * (host->bus_hz / 1000000) + 999) / 1000; | 
|  | 407 | } | 
|  | 408 |  | 
|  | 409 | static void atmci_set_timeout(struct atmel_mci *host, | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 410 | struct atmel_mci_slot *slot, struct mmc_data *data) | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 411 | { | 
|  | 412 | static unsigned	dtomul_to_shift[] = { | 
|  | 413 | 0, 4, 7, 8, 10, 12, 16, 20 | 
|  | 414 | }; | 
|  | 415 | unsigned	timeout; | 
|  | 416 | unsigned	dtocyc; | 
|  | 417 | unsigned	dtomul; | 
|  | 418 |  | 
|  | 419 | timeout = ns_to_clocks(host, data->timeout_ns) + data->timeout_clks; | 
|  | 420 |  | 
|  | 421 | for (dtomul = 0; dtomul < 8; dtomul++) { | 
|  | 422 | unsigned shift = dtomul_to_shift[dtomul]; | 
|  | 423 | dtocyc = (timeout + (1 << shift) - 1) >> shift; | 
|  | 424 | if (dtocyc < 15) | 
|  | 425 | break; | 
|  | 426 | } | 
|  | 427 |  | 
|  | 428 | if (dtomul >= 8) { | 
|  | 429 | dtomul = 7; | 
|  | 430 | dtocyc = 15; | 
|  | 431 | } | 
|  | 432 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 433 | dev_vdbg(&slot->mmc->class_dev, "setting timeout to %u cycles\n", | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 434 | dtocyc << dtomul_to_shift[dtomul]); | 
|  | 435 | mci_writel(host, DTOR, (MCI_DTOMUL(dtomul) | MCI_DTOCYC(dtocyc))); | 
|  | 436 | } | 
|  | 437 |  | 
|  | 438 | /* | 
|  | 439 | * Return mask with command flags to be enabled for this command. | 
|  | 440 | */ | 
|  | 441 | static u32 atmci_prepare_command(struct mmc_host *mmc, | 
|  | 442 | struct mmc_command *cmd) | 
|  | 443 | { | 
|  | 444 | struct mmc_data	*data; | 
|  | 445 | u32		cmdr; | 
|  | 446 |  | 
|  | 447 | cmd->error = -EINPROGRESS; | 
|  | 448 |  | 
|  | 449 | cmdr = MCI_CMDR_CMDNB(cmd->opcode); | 
|  | 450 |  | 
|  | 451 | if (cmd->flags & MMC_RSP_PRESENT) { | 
|  | 452 | if (cmd->flags & MMC_RSP_136) | 
|  | 453 | cmdr |= MCI_CMDR_RSPTYP_136BIT; | 
|  | 454 | else | 
|  | 455 | cmdr |= MCI_CMDR_RSPTYP_48BIT; | 
|  | 456 | } | 
|  | 457 |  | 
|  | 458 | /* | 
|  | 459 | * This should really be MAXLAT_5 for CMD2 and ACMD41, but | 
|  | 460 | * it's too difficult to determine whether this is an ACMD or | 
|  | 461 | * not. Better make it 64. | 
|  | 462 | */ | 
|  | 463 | cmdr |= MCI_CMDR_MAXLAT_64CYC; | 
|  | 464 |  | 
|  | 465 | if (mmc->ios.bus_mode == MMC_BUSMODE_OPENDRAIN) | 
|  | 466 | cmdr |= MCI_CMDR_OPDCMD; | 
|  | 467 |  | 
|  | 468 | data = cmd->data; | 
|  | 469 | if (data) { | 
|  | 470 | cmdr |= MCI_CMDR_START_XFER; | 
|  | 471 | if (data->flags & MMC_DATA_STREAM) | 
|  | 472 | cmdr |= MCI_CMDR_STREAM; | 
|  | 473 | else if (data->blocks > 1) | 
|  | 474 | cmdr |= MCI_CMDR_MULTI_BLOCK; | 
|  | 475 | else | 
|  | 476 | cmdr |= MCI_CMDR_BLOCK; | 
|  | 477 |  | 
|  | 478 | if (data->flags & MMC_DATA_READ) | 
|  | 479 | cmdr |= MCI_CMDR_TRDIR_READ; | 
|  | 480 | } | 
|  | 481 |  | 
|  | 482 | return cmdr; | 
|  | 483 | } | 
|  | 484 |  | 
|  | 485 | static void atmci_start_command(struct atmel_mci *host, | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 486 | struct mmc_command *cmd, u32 cmd_flags) | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 487 | { | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 488 | WARN_ON(host->cmd); | 
|  | 489 | host->cmd = cmd; | 
|  | 490 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 491 | dev_vdbg(&host->pdev->dev, | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 492 | "start command: ARGR=0x%08x CMDR=0x%08x\n", | 
|  | 493 | cmd->arg, cmd_flags); | 
|  | 494 |  | 
|  | 495 | mci_writel(host, ARGR, cmd->arg); | 
|  | 496 | mci_writel(host, CMDR, cmd_flags); | 
|  | 497 | } | 
|  | 498 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 499 | static void send_stop_cmd(struct atmel_mci *host, struct mmc_data *data) | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 500 | { | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 501 | atmci_start_command(host, data->stop, host->stop_cmdr); | 
|  | 502 | mci_writel(host, IER, MCI_CMDRDY); | 
|  | 503 | } | 
|  | 504 |  | 
| Haavard Skinnemoen | 65e8b08 | 2008-07-30 20:29:03 +0200 | [diff] [blame] | 505 | #ifdef CONFIG_MMC_ATMELMCI_DMA | 
|  | 506 | static void atmci_dma_cleanup(struct atmel_mci *host) | 
|  | 507 | { | 
|  | 508 | struct mmc_data			*data = host->data; | 
|  | 509 |  | 
|  | 510 | dma_unmap_sg(&host->pdev->dev, data->sg, data->sg_len, | 
|  | 511 | ((data->flags & MMC_DATA_WRITE) | 
|  | 512 | ? DMA_TO_DEVICE : DMA_FROM_DEVICE)); | 
|  | 513 | } | 
|  | 514 |  | 
|  | 515 | static void atmci_stop_dma(struct atmel_mci *host) | 
|  | 516 | { | 
|  | 517 | struct dma_chan *chan = host->data_chan; | 
|  | 518 |  | 
|  | 519 | if (chan) { | 
|  | 520 | chan->device->device_terminate_all(chan); | 
|  | 521 | atmci_dma_cleanup(host); | 
|  | 522 | } else { | 
|  | 523 | /* Data transfer was stopped by the interrupt handler */ | 
|  | 524 | atmci_set_pending(host, EVENT_XFER_COMPLETE); | 
|  | 525 | mci_writel(host, IER, MCI_NOTBUSY); | 
|  | 526 | } | 
|  | 527 | } | 
|  | 528 |  | 
|  | 529 | /* This function is called by the DMA driver from tasklet context. */ | 
|  | 530 | static void atmci_dma_complete(void *arg) | 
|  | 531 | { | 
|  | 532 | struct atmel_mci	*host = arg; | 
|  | 533 | struct mmc_data		*data = host->data; | 
|  | 534 |  | 
|  | 535 | dev_vdbg(&host->pdev->dev, "DMA complete\n"); | 
|  | 536 |  | 
|  | 537 | atmci_dma_cleanup(host); | 
|  | 538 |  | 
|  | 539 | /* | 
|  | 540 | * If the card was removed, data will be NULL. No point trying | 
|  | 541 | * to send the stop command or waiting for NBUSY in this case. | 
|  | 542 | */ | 
|  | 543 | if (data) { | 
|  | 544 | atmci_set_pending(host, EVENT_XFER_COMPLETE); | 
|  | 545 | tasklet_schedule(&host->tasklet); | 
|  | 546 |  | 
|  | 547 | /* | 
|  | 548 | * Regardless of what the documentation says, we have | 
|  | 549 | * to wait for NOTBUSY even after block read | 
|  | 550 | * operations. | 
|  | 551 | * | 
|  | 552 | * When the DMA transfer is complete, the controller | 
|  | 553 | * may still be reading the CRC from the card, i.e. | 
|  | 554 | * the data transfer is still in progress and we | 
|  | 555 | * haven't seen all the potential error bits yet. | 
|  | 556 | * | 
|  | 557 | * The interrupt handler will schedule a different | 
|  | 558 | * tasklet to finish things up when the data transfer | 
|  | 559 | * is completely done. | 
|  | 560 | * | 
|  | 561 | * We may not complete the mmc request here anyway | 
|  | 562 | * because the mmc layer may call back and cause us to | 
|  | 563 | * violate the "don't submit new operations from the | 
|  | 564 | * completion callback" rule of the dma engine | 
|  | 565 | * framework. | 
|  | 566 | */ | 
|  | 567 | mci_writel(host, IER, MCI_NOTBUSY); | 
|  | 568 | } | 
|  | 569 | } | 
|  | 570 |  | 
|  | 571 | static int | 
|  | 572 | atmci_submit_data_dma(struct atmel_mci *host, struct mmc_data *data) | 
|  | 573 | { | 
|  | 574 | struct dma_chan			*chan; | 
|  | 575 | struct dma_async_tx_descriptor	*desc; | 
|  | 576 | struct scatterlist		*sg; | 
|  | 577 | unsigned int			i; | 
|  | 578 | enum dma_data_direction		direction; | 
|  | 579 |  | 
|  | 580 | /* | 
|  | 581 | * We don't do DMA on "complex" transfers, i.e. with | 
|  | 582 | * non-word-aligned buffers or lengths. Also, we don't bother | 
|  | 583 | * with all the DMA setup overhead for short transfers. | 
|  | 584 | */ | 
|  | 585 | if (data->blocks * data->blksz < ATMCI_DMA_THRESHOLD) | 
|  | 586 | return -EINVAL; | 
|  | 587 | if (data->blksz & 3) | 
|  | 588 | return -EINVAL; | 
|  | 589 |  | 
|  | 590 | for_each_sg(data->sg, sg, data->sg_len, i) { | 
|  | 591 | if (sg->offset & 3 || sg->length & 3) | 
|  | 592 | return -EINVAL; | 
|  | 593 | } | 
|  | 594 |  | 
|  | 595 | /* If we don't have a channel, we can't do DMA */ | 
|  | 596 | chan = host->dma.chan; | 
| Dan Williams | 6f49a57 | 2009-01-06 11:38:14 -0700 | [diff] [blame] | 597 | if (chan) | 
| Haavard Skinnemoen | 65e8b08 | 2008-07-30 20:29:03 +0200 | [diff] [blame] | 598 | host->data_chan = chan; | 
| Haavard Skinnemoen | 65e8b08 | 2008-07-30 20:29:03 +0200 | [diff] [blame] | 599 |  | 
|  | 600 | if (!chan) | 
|  | 601 | return -ENODEV; | 
|  | 602 |  | 
|  | 603 | if (data->flags & MMC_DATA_READ) | 
|  | 604 | direction = DMA_FROM_DEVICE; | 
|  | 605 | else | 
|  | 606 | direction = DMA_TO_DEVICE; | 
|  | 607 |  | 
|  | 608 | desc = chan->device->device_prep_slave_sg(chan, | 
|  | 609 | data->sg, data->sg_len, direction, | 
|  | 610 | DMA_PREP_INTERRUPT | DMA_CTRL_ACK); | 
|  | 611 | if (!desc) | 
|  | 612 | return -ENOMEM; | 
|  | 613 |  | 
|  | 614 | host->dma.data_desc = desc; | 
|  | 615 | desc->callback = atmci_dma_complete; | 
|  | 616 | desc->callback_param = host; | 
|  | 617 | desc->tx_submit(desc); | 
|  | 618 |  | 
|  | 619 | /* Go! */ | 
|  | 620 | chan->device->device_issue_pending(chan); | 
|  | 621 |  | 
|  | 622 | return 0; | 
|  | 623 | } | 
|  | 624 |  | 
|  | 625 | #else /* CONFIG_MMC_ATMELMCI_DMA */ | 
|  | 626 |  | 
|  | 627 | static int atmci_submit_data_dma(struct atmel_mci *host, struct mmc_data *data) | 
|  | 628 | { | 
|  | 629 | return -ENOSYS; | 
|  | 630 | } | 
|  | 631 |  | 
|  | 632 | static void atmci_stop_dma(struct atmel_mci *host) | 
|  | 633 | { | 
|  | 634 | /* Data transfer was stopped by the interrupt handler */ | 
|  | 635 | atmci_set_pending(host, EVENT_XFER_COMPLETE); | 
|  | 636 | mci_writel(host, IER, MCI_NOTBUSY); | 
|  | 637 | } | 
|  | 638 |  | 
|  | 639 | #endif /* CONFIG_MMC_ATMELMCI_DMA */ | 
|  | 640 |  | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 641 | /* | 
|  | 642 | * Returns a mask of interrupt flags to be enabled after the whole | 
|  | 643 | * request has been prepared. | 
|  | 644 | */ | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 645 | static u32 atmci_submit_data(struct atmel_mci *host, struct mmc_data *data) | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 646 | { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 647 | u32 iflags; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 648 |  | 
|  | 649 | data->error = -EINPROGRESS; | 
|  | 650 |  | 
|  | 651 | WARN_ON(host->data); | 
|  | 652 | host->sg = NULL; | 
|  | 653 | host->data = data; | 
|  | 654 |  | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 655 | iflags = ATMCI_DATA_ERROR_FLAGS; | 
| Haavard Skinnemoen | 65e8b08 | 2008-07-30 20:29:03 +0200 | [diff] [blame] | 656 | if (atmci_submit_data_dma(host, data)) { | 
|  | 657 | host->data_chan = NULL; | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 658 |  | 
| Haavard Skinnemoen | 65e8b08 | 2008-07-30 20:29:03 +0200 | [diff] [blame] | 659 | /* | 
|  | 660 | * Errata: MMC data write operation with less than 12 | 
|  | 661 | * bytes is impossible. | 
|  | 662 | * | 
|  | 663 | * Errata: MCI Transmit Data Register (TDR) FIFO | 
|  | 664 | * corruption when length is not multiple of 4. | 
|  | 665 | */ | 
|  | 666 | if (data->blocks * data->blksz < 12 | 
|  | 667 | || (data->blocks * data->blksz) & 3) | 
|  | 668 | host->need_reset = true; | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 669 |  | 
| Haavard Skinnemoen | 65e8b08 | 2008-07-30 20:29:03 +0200 | [diff] [blame] | 670 | host->sg = data->sg; | 
|  | 671 | host->pio_offset = 0; | 
|  | 672 | if (data->flags & MMC_DATA_READ) | 
|  | 673 | iflags |= MCI_RXRDY; | 
|  | 674 | else | 
|  | 675 | iflags |= MCI_TXRDY; | 
|  | 676 | } | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 677 |  | 
|  | 678 | return iflags; | 
|  | 679 | } | 
|  | 680 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 681 | static void atmci_start_request(struct atmel_mci *host, | 
|  | 682 | struct atmel_mci_slot *slot) | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 683 | { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 684 | struct mmc_request	*mrq; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 685 | struct mmc_command	*cmd; | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 686 | struct mmc_data		*data; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 687 | u32			iflags; | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 688 | u32			cmdflags; | 
|  | 689 |  | 
|  | 690 | mrq = slot->mrq; | 
|  | 691 | host->cur_slot = slot; | 
|  | 692 | host->mrq = mrq; | 
|  | 693 |  | 
|  | 694 | host->pending_events = 0; | 
|  | 695 | host->completed_events = 0; | 
| Haavard Skinnemoen | ca55f46 | 2008-10-05 15:16:59 +0200 | [diff] [blame] | 696 | host->data_status = 0; | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 697 |  | 
|  | 698 | if (host->need_reset) { | 
|  | 699 | mci_writel(host, CR, MCI_CR_SWRST); | 
|  | 700 | mci_writel(host, CR, MCI_CR_MCIEN); | 
|  | 701 | mci_writel(host, MR, host->mode_reg); | 
|  | 702 | host->need_reset = false; | 
|  | 703 | } | 
|  | 704 | mci_writel(host, SDCR, slot->sdc_reg); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 705 |  | 
|  | 706 | iflags = mci_readl(host, IMR); | 
|  | 707 | if (iflags) | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 708 | dev_warn(&slot->mmc->class_dev, "WARNING: IMR=0x%08x\n", | 
|  | 709 | iflags); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 710 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 711 | if (unlikely(test_and_clear_bit(ATMCI_CARD_NEED_INIT, &slot->flags))) { | 
|  | 712 | /* Send init sequence (74 clock cycles) */ | 
|  | 713 | mci_writel(host, CMDR, MCI_CMDR_SPCMD_INIT); | 
|  | 714 | while (!(mci_readl(host, SR) & MCI_CMDRDY)) | 
|  | 715 | cpu_relax(); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 716 | } | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 717 | data = mrq->data; | 
|  | 718 | if (data) { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 719 | atmci_set_timeout(host, slot, data); | 
| Haavard Skinnemoen | a252e3e | 2008-10-03 14:46:17 +0200 | [diff] [blame] | 720 |  | 
|  | 721 | /* Must set block count/size before sending command */ | 
|  | 722 | mci_writel(host, BLKR, MCI_BCNT(data->blocks) | 
|  | 723 | | MCI_BLKLEN(data->blksz)); | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 724 | dev_vdbg(&slot->mmc->class_dev, "BLKR=0x%08x\n", | 
|  | 725 | MCI_BCNT(data->blocks) | MCI_BLKLEN(data->blksz)); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 726 | } | 
|  | 727 |  | 
|  | 728 | iflags = MCI_CMDRDY; | 
|  | 729 | cmd = mrq->cmd; | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 730 | cmdflags = atmci_prepare_command(slot->mmc, cmd); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 731 | atmci_start_command(host, cmd, cmdflags); | 
|  | 732 |  | 
|  | 733 | if (data) | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 734 | iflags |= atmci_submit_data(host, data); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 735 |  | 
|  | 736 | if (mrq->stop) { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 737 | host->stop_cmdr = atmci_prepare_command(slot->mmc, mrq->stop); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 738 | host->stop_cmdr |= MCI_CMDR_STOP_XFER; | 
|  | 739 | if (!(data->flags & MMC_DATA_WRITE)) | 
|  | 740 | host->stop_cmdr |= MCI_CMDR_TRDIR_READ; | 
|  | 741 | if (data->flags & MMC_DATA_STREAM) | 
|  | 742 | host->stop_cmdr |= MCI_CMDR_STREAM; | 
|  | 743 | else | 
|  | 744 | host->stop_cmdr |= MCI_CMDR_MULTI_BLOCK; | 
|  | 745 | } | 
|  | 746 |  | 
|  | 747 | /* | 
|  | 748 | * We could have enabled interrupts earlier, but I suspect | 
|  | 749 | * that would open up a nice can of interesting race | 
|  | 750 | * conditions (e.g. command and data complete, but stop not | 
|  | 751 | * prepared yet.) | 
|  | 752 | */ | 
|  | 753 | mci_writel(host, IER, iflags); | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 754 | } | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 755 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 756 | static void atmci_queue_request(struct atmel_mci *host, | 
|  | 757 | struct atmel_mci_slot *slot, struct mmc_request *mrq) | 
|  | 758 | { | 
|  | 759 | dev_vdbg(&slot->mmc->class_dev, "queue request: state=%d\n", | 
|  | 760 | host->state); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 761 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 762 | spin_lock_bh(&host->lock); | 
|  | 763 | slot->mrq = mrq; | 
|  | 764 | if (host->state == STATE_IDLE) { | 
|  | 765 | host->state = STATE_SENDING_CMD; | 
|  | 766 | atmci_start_request(host, slot); | 
|  | 767 | } else { | 
|  | 768 | list_add_tail(&slot->queue_node, &host->queue); | 
|  | 769 | } | 
|  | 770 | spin_unlock_bh(&host->lock); | 
|  | 771 | } | 
|  | 772 |  | 
|  | 773 | static void atmci_request(struct mmc_host *mmc, struct mmc_request *mrq) | 
|  | 774 | { | 
|  | 775 | struct atmel_mci_slot	*slot = mmc_priv(mmc); | 
|  | 776 | struct atmel_mci	*host = slot->host; | 
|  | 777 | struct mmc_data		*data; | 
|  | 778 |  | 
|  | 779 | WARN_ON(slot->mrq); | 
|  | 780 |  | 
|  | 781 | /* | 
|  | 782 | * We may "know" the card is gone even though there's still an | 
|  | 783 | * electrical connection. If so, we really need to communicate | 
|  | 784 | * this to the MMC core since there won't be any more | 
|  | 785 | * interrupts as the card is completely removed. Otherwise, | 
|  | 786 | * the MMC core might believe the card is still there even | 
|  | 787 | * though the card was just removed very slowly. | 
|  | 788 | */ | 
|  | 789 | if (!test_bit(ATMCI_CARD_PRESENT, &slot->flags)) { | 
|  | 790 | mrq->cmd->error = -ENOMEDIUM; | 
|  | 791 | mmc_request_done(mmc, mrq); | 
|  | 792 | return; | 
|  | 793 | } | 
|  | 794 |  | 
|  | 795 | /* We don't support multiple blocks of weird lengths. */ | 
|  | 796 | data = mrq->data; | 
|  | 797 | if (data && data->blocks > 1 && data->blksz & 3) { | 
|  | 798 | mrq->cmd->error = -EINVAL; | 
|  | 799 | mmc_request_done(mmc, mrq); | 
|  | 800 | } | 
|  | 801 |  | 
|  | 802 | atmci_queue_request(host, slot, mrq); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 803 | } | 
|  | 804 |  | 
|  | 805 | static void atmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) | 
|  | 806 | { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 807 | struct atmel_mci_slot	*slot = mmc_priv(mmc); | 
|  | 808 | struct atmel_mci	*host = slot->host; | 
|  | 809 | unsigned int		i; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 810 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 811 | slot->sdc_reg &= ~MCI_SDCBUS_MASK; | 
| Haavard Skinnemoen | 945533b | 2008-10-03 17:48:16 +0200 | [diff] [blame] | 812 | switch (ios->bus_width) { | 
|  | 813 | case MMC_BUS_WIDTH_1: | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 814 | slot->sdc_reg |= MCI_SDCBUS_1BIT; | 
| Haavard Skinnemoen | 945533b | 2008-10-03 17:48:16 +0200 | [diff] [blame] | 815 | break; | 
|  | 816 | case MMC_BUS_WIDTH_4: | 
| Hans-Christian Egtvedt | 32ab83a | 2009-03-24 11:06:06 +0100 | [diff] [blame] | 817 | slot->sdc_reg |= MCI_SDCBUS_4BIT; | 
| Haavard Skinnemoen | 945533b | 2008-10-03 17:48:16 +0200 | [diff] [blame] | 818 | break; | 
|  | 819 | } | 
|  | 820 |  | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 821 | if (ios->clock) { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 822 | unsigned int clock_min = ~0U; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 823 | u32 clkdiv; | 
|  | 824 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 825 | spin_lock_bh(&host->lock); | 
|  | 826 | if (!host->mode_reg) { | 
| Haavard Skinnemoen | 945533b | 2008-10-03 17:48:16 +0200 | [diff] [blame] | 827 | clk_enable(host->mck); | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 828 | mci_writel(host, CR, MCI_CR_SWRST); | 
|  | 829 | mci_writel(host, CR, MCI_CR_MCIEN); | 
|  | 830 | } | 
| Haavard Skinnemoen | 945533b | 2008-10-03 17:48:16 +0200 | [diff] [blame] | 831 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 832 | /* | 
|  | 833 | * Use mirror of ios->clock to prevent race with mmc | 
|  | 834 | * core ios update when finding the minimum. | 
|  | 835 | */ | 
|  | 836 | slot->clock = ios->clock; | 
|  | 837 | for (i = 0; i < ATMEL_MCI_MAX_NR_SLOTS; i++) { | 
|  | 838 | if (host->slot[i] && host->slot[i]->clock | 
|  | 839 | && host->slot[i]->clock < clock_min) | 
|  | 840 | clock_min = host->slot[i]->clock; | 
|  | 841 | } | 
|  | 842 |  | 
|  | 843 | /* Calculate clock divider */ | 
|  | 844 | clkdiv = DIV_ROUND_UP(host->bus_hz, 2 * clock_min) - 1; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 845 | if (clkdiv > 255) { | 
|  | 846 | dev_warn(&mmc->class_dev, | 
|  | 847 | "clock %u too slow; using %lu\n", | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 848 | clock_min, host->bus_hz / (2 * 256)); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 849 | clkdiv = 255; | 
|  | 850 | } | 
|  | 851 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 852 | /* | 
|  | 853 | * WRPROOF and RDPROOF prevent overruns/underruns by | 
|  | 854 | * stopping the clock when the FIFO is full/empty. | 
|  | 855 | * This state is not expected to last for long. | 
|  | 856 | */ | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 857 | host->mode_reg = MCI_MR_CLKDIV(clkdiv) | MCI_MR_WRPROOF | 
|  | 858 | | MCI_MR_RDPROOF; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 859 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 860 | if (list_empty(&host->queue)) | 
|  | 861 | mci_writel(host, MR, host->mode_reg); | 
|  | 862 | else | 
|  | 863 | host->need_clock_update = true; | 
|  | 864 |  | 
|  | 865 | spin_unlock_bh(&host->lock); | 
| Haavard Skinnemoen | 945533b | 2008-10-03 17:48:16 +0200 | [diff] [blame] | 866 | } else { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 867 | bool any_slot_active = false; | 
|  | 868 |  | 
|  | 869 | spin_lock_bh(&host->lock); | 
|  | 870 | slot->clock = 0; | 
|  | 871 | for (i = 0; i < ATMEL_MCI_MAX_NR_SLOTS; i++) { | 
|  | 872 | if (host->slot[i] && host->slot[i]->clock) { | 
|  | 873 | any_slot_active = true; | 
|  | 874 | break; | 
|  | 875 | } | 
| Haavard Skinnemoen | 945533b | 2008-10-03 17:48:16 +0200 | [diff] [blame] | 876 | } | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 877 | if (!any_slot_active) { | 
|  | 878 | mci_writel(host, CR, MCI_CR_MCIDIS); | 
|  | 879 | if (host->mode_reg) { | 
|  | 880 | mci_readl(host, MR); | 
|  | 881 | clk_disable(host->mck); | 
|  | 882 | } | 
|  | 883 | host->mode_reg = 0; | 
|  | 884 | } | 
|  | 885 | spin_unlock_bh(&host->lock); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 886 | } | 
|  | 887 |  | 
|  | 888 | switch (ios->power_mode) { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 889 | case MMC_POWER_UP: | 
|  | 890 | set_bit(ATMCI_CARD_NEED_INIT, &slot->flags); | 
|  | 891 | break; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 892 | default: | 
|  | 893 | /* | 
|  | 894 | * TODO: None of the currently available AVR32-based | 
|  | 895 | * boards allow MMC power to be turned off. Implement | 
|  | 896 | * power control when this can be tested properly. | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 897 | * | 
|  | 898 | * We also need to hook this into the clock management | 
|  | 899 | * somehow so that newly inserted cards aren't | 
|  | 900 | * subjected to a fast clock before we have a chance | 
|  | 901 | * to figure out what the maximum rate is. Currently, | 
|  | 902 | * there's no way to avoid this, and there never will | 
|  | 903 | * be for boards that don't support power control. | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 904 | */ | 
|  | 905 | break; | 
|  | 906 | } | 
|  | 907 | } | 
|  | 908 |  | 
|  | 909 | static int atmci_get_ro(struct mmc_host *mmc) | 
|  | 910 | { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 911 | int			read_only = -ENOSYS; | 
|  | 912 | struct atmel_mci_slot	*slot = mmc_priv(mmc); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 913 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 914 | if (gpio_is_valid(slot->wp_pin)) { | 
|  | 915 | read_only = gpio_get_value(slot->wp_pin); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 916 | dev_dbg(&mmc->class_dev, "card is %s\n", | 
|  | 917 | read_only ? "read-only" : "read-write"); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 918 | } | 
|  | 919 |  | 
|  | 920 | return read_only; | 
|  | 921 | } | 
|  | 922 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 923 | static int atmci_get_cd(struct mmc_host *mmc) | 
|  | 924 | { | 
|  | 925 | int			present = -ENOSYS; | 
|  | 926 | struct atmel_mci_slot	*slot = mmc_priv(mmc); | 
|  | 927 |  | 
|  | 928 | if (gpio_is_valid(slot->detect_pin)) { | 
| Jonas Larsson | 1c1452b | 2009-03-31 11:16:48 +0200 | [diff] [blame] | 929 | present = !(gpio_get_value(slot->detect_pin) ^ | 
|  | 930 | slot->detect_is_active_high); | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 931 | dev_dbg(&mmc->class_dev, "card is %spresent\n", | 
|  | 932 | present ? "" : "not "); | 
|  | 933 | } | 
|  | 934 |  | 
|  | 935 | return present; | 
|  | 936 | } | 
|  | 937 |  | 
|  | 938 | static const struct mmc_host_ops atmci_ops = { | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 939 | .request	= atmci_request, | 
|  | 940 | .set_ios	= atmci_set_ios, | 
|  | 941 | .get_ro		= atmci_get_ro, | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 942 | .get_cd		= atmci_get_cd, | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 943 | }; | 
|  | 944 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 945 | /* Called with host->lock held */ | 
|  | 946 | static void atmci_request_end(struct atmel_mci *host, struct mmc_request *mrq) | 
|  | 947 | __releases(&host->lock) | 
|  | 948 | __acquires(&host->lock) | 
|  | 949 | { | 
|  | 950 | struct atmel_mci_slot	*slot = NULL; | 
|  | 951 | struct mmc_host		*prev_mmc = host->cur_slot->mmc; | 
|  | 952 |  | 
|  | 953 | WARN_ON(host->cmd || host->data); | 
|  | 954 |  | 
|  | 955 | /* | 
|  | 956 | * Update the MMC clock rate if necessary. This may be | 
|  | 957 | * necessary if set_ios() is called when a different slot is | 
|  | 958 | * busy transfering data. | 
|  | 959 | */ | 
|  | 960 | if (host->need_clock_update) | 
|  | 961 | mci_writel(host, MR, host->mode_reg); | 
|  | 962 |  | 
|  | 963 | host->cur_slot->mrq = NULL; | 
|  | 964 | host->mrq = NULL; | 
|  | 965 | if (!list_empty(&host->queue)) { | 
|  | 966 | slot = list_entry(host->queue.next, | 
|  | 967 | struct atmel_mci_slot, queue_node); | 
|  | 968 | list_del(&slot->queue_node); | 
|  | 969 | dev_vdbg(&host->pdev->dev, "list not empty: %s is next\n", | 
|  | 970 | mmc_hostname(slot->mmc)); | 
|  | 971 | host->state = STATE_SENDING_CMD; | 
|  | 972 | atmci_start_request(host, slot); | 
|  | 973 | } else { | 
|  | 974 | dev_vdbg(&host->pdev->dev, "list empty\n"); | 
|  | 975 | host->state = STATE_IDLE; | 
|  | 976 | } | 
|  | 977 |  | 
|  | 978 | spin_unlock(&host->lock); | 
|  | 979 | mmc_request_done(prev_mmc, mrq); | 
|  | 980 | spin_lock(&host->lock); | 
|  | 981 | } | 
|  | 982 |  | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 983 | static void atmci_command_complete(struct atmel_mci *host, | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 984 | struct mmc_command *cmd) | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 985 | { | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 986 | u32		status = host->cmd_status; | 
|  | 987 |  | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 988 | /* Read the response from the card (up to 16 bytes) */ | 
|  | 989 | cmd->resp[0] = mci_readl(host, RSPR); | 
|  | 990 | cmd->resp[1] = mci_readl(host, RSPR); | 
|  | 991 | cmd->resp[2] = mci_readl(host, RSPR); | 
|  | 992 | cmd->resp[3] = mci_readl(host, RSPR); | 
|  | 993 |  | 
|  | 994 | if (status & MCI_RTOE) | 
|  | 995 | cmd->error = -ETIMEDOUT; | 
|  | 996 | else if ((cmd->flags & MMC_RSP_CRC) && (status & MCI_RCRCE)) | 
|  | 997 | cmd->error = -EILSEQ; | 
|  | 998 | else if (status & (MCI_RINDE | MCI_RDIRE | MCI_RENDE)) | 
|  | 999 | cmd->error = -EIO; | 
|  | 1000 | else | 
|  | 1001 | cmd->error = 0; | 
|  | 1002 |  | 
|  | 1003 | if (cmd->error) { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1004 | dev_dbg(&host->pdev->dev, | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1005 | "command error: status=0x%08x\n", status); | 
|  | 1006 |  | 
|  | 1007 | if (cmd->data) { | 
|  | 1008 | host->data = NULL; | 
| Haavard Skinnemoen | 65e8b08 | 2008-07-30 20:29:03 +0200 | [diff] [blame] | 1009 | atmci_stop_dma(host); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1010 | mci_writel(host, IDR, MCI_NOTBUSY | 
|  | 1011 | | MCI_TXRDY | MCI_RXRDY | 
|  | 1012 | | ATMCI_DATA_ERROR_FLAGS); | 
|  | 1013 | } | 
|  | 1014 | } | 
|  | 1015 | } | 
|  | 1016 |  | 
|  | 1017 | static void atmci_detect_change(unsigned long data) | 
|  | 1018 | { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1019 | struct atmel_mci_slot	*slot = (struct atmel_mci_slot *)data; | 
|  | 1020 | bool			present; | 
|  | 1021 | bool			present_old; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1022 |  | 
|  | 1023 | /* | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1024 | * atmci_cleanup_slot() sets the ATMCI_SHUTDOWN flag before | 
|  | 1025 | * freeing the interrupt. We must not re-enable the interrupt | 
|  | 1026 | * if it has been freed, and if we're shutting down, it | 
|  | 1027 | * doesn't really matter whether the card is present or not. | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1028 | */ | 
|  | 1029 | smp_rmb(); | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1030 | if (test_bit(ATMCI_SHUTDOWN, &slot->flags)) | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1031 | return; | 
|  | 1032 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1033 | enable_irq(gpio_to_irq(slot->detect_pin)); | 
| Jonas Larsson | 1c1452b | 2009-03-31 11:16:48 +0200 | [diff] [blame] | 1034 | present = !(gpio_get_value(slot->detect_pin) ^ | 
|  | 1035 | slot->detect_is_active_high); | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1036 | present_old = test_bit(ATMCI_CARD_PRESENT, &slot->flags); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1037 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1038 | dev_vdbg(&slot->mmc->class_dev, "detect change: %d (was %d)\n", | 
|  | 1039 | present, present_old); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1040 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1041 | if (present != present_old) { | 
|  | 1042 | struct atmel_mci	*host = slot->host; | 
|  | 1043 | struct mmc_request	*mrq; | 
|  | 1044 |  | 
|  | 1045 | dev_dbg(&slot->mmc->class_dev, "card %s\n", | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1046 | present ? "inserted" : "removed"); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1047 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1048 | spin_lock(&host->lock); | 
|  | 1049 |  | 
|  | 1050 | if (!present) | 
|  | 1051 | clear_bit(ATMCI_CARD_PRESENT, &slot->flags); | 
|  | 1052 | else | 
|  | 1053 | set_bit(ATMCI_CARD_PRESENT, &slot->flags); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1054 |  | 
|  | 1055 | /* Clean up queue if present */ | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1056 | mrq = slot->mrq; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1057 | if (mrq) { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1058 | if (mrq == host->mrq) { | 
|  | 1059 | /* | 
|  | 1060 | * Reset controller to terminate any ongoing | 
|  | 1061 | * commands or data transfers. | 
|  | 1062 | */ | 
|  | 1063 | mci_writel(host, CR, MCI_CR_SWRST); | 
|  | 1064 | mci_writel(host, CR, MCI_CR_MCIEN); | 
|  | 1065 | mci_writel(host, MR, host->mode_reg); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1066 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1067 | host->data = NULL; | 
|  | 1068 | host->cmd = NULL; | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1069 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1070 | switch (host->state) { | 
|  | 1071 | case STATE_IDLE: | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1072 | break; | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1073 | case STATE_SENDING_CMD: | 
|  | 1074 | mrq->cmd->error = -ENOMEDIUM; | 
|  | 1075 | if (!mrq->data) | 
|  | 1076 | break; | 
|  | 1077 | /* fall through */ | 
|  | 1078 | case STATE_SENDING_DATA: | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1079 | mrq->data->error = -ENOMEDIUM; | 
| Haavard Skinnemoen | 65e8b08 | 2008-07-30 20:29:03 +0200 | [diff] [blame] | 1080 | atmci_stop_dma(host); | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1081 | break; | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1082 | case STATE_DATA_BUSY: | 
|  | 1083 | case STATE_DATA_ERROR: | 
|  | 1084 | if (mrq->data->error == -EINPROGRESS) | 
|  | 1085 | mrq->data->error = -ENOMEDIUM; | 
|  | 1086 | if (!mrq->stop) | 
|  | 1087 | break; | 
|  | 1088 | /* fall through */ | 
|  | 1089 | case STATE_SENDING_STOP: | 
|  | 1090 | mrq->stop->error = -ENOMEDIUM; | 
|  | 1091 | break; | 
|  | 1092 | } | 
|  | 1093 |  | 
|  | 1094 | atmci_request_end(host, mrq); | 
|  | 1095 | } else { | 
|  | 1096 | list_del(&slot->queue_node); | 
|  | 1097 | mrq->cmd->error = -ENOMEDIUM; | 
|  | 1098 | if (mrq->data) | 
|  | 1099 | mrq->data->error = -ENOMEDIUM; | 
|  | 1100 | if (mrq->stop) | 
|  | 1101 | mrq->stop->error = -ENOMEDIUM; | 
|  | 1102 |  | 
|  | 1103 | spin_unlock(&host->lock); | 
|  | 1104 | mmc_request_done(slot->mmc, mrq); | 
|  | 1105 | spin_lock(&host->lock); | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1106 | } | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1107 | } | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1108 | spin_unlock(&host->lock); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1109 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1110 | mmc_detect_change(slot->mmc, 0); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1111 | } | 
|  | 1112 | } | 
|  | 1113 |  | 
|  | 1114 | static void atmci_tasklet_func(unsigned long priv) | 
|  | 1115 | { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1116 | struct atmel_mci	*host = (struct atmel_mci *)priv; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1117 | struct mmc_request	*mrq = host->mrq; | 
|  | 1118 | struct mmc_data		*data = host->data; | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1119 | struct mmc_command	*cmd = host->cmd; | 
|  | 1120 | enum atmel_mci_state	state = host->state; | 
|  | 1121 | enum atmel_mci_state	prev_state; | 
|  | 1122 | u32			status; | 
|  | 1123 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1124 | spin_lock(&host->lock); | 
|  | 1125 |  | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1126 | state = host->state; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1127 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1128 | dev_vdbg(&host->pdev->dev, | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1129 | "tasklet: state %u pending/completed/mask %lx/%lx/%x\n", | 
|  | 1130 | state, host->pending_events, host->completed_events, | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1131 | mci_readl(host, IMR)); | 
|  | 1132 |  | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1133 | do { | 
|  | 1134 | prev_state = state; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1135 |  | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1136 | switch (state) { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1137 | case STATE_IDLE: | 
|  | 1138 | break; | 
|  | 1139 |  | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1140 | case STATE_SENDING_CMD: | 
|  | 1141 | if (!atmci_test_and_clear_pending(host, | 
|  | 1142 | EVENT_CMD_COMPLETE)) | 
|  | 1143 | break; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1144 |  | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1145 | host->cmd = NULL; | 
|  | 1146 | atmci_set_completed(host, EVENT_CMD_COMPLETE); | 
|  | 1147 | atmci_command_complete(host, mrq->cmd); | 
|  | 1148 | if (!mrq->data || cmd->error) { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1149 | atmci_request_end(host, host->mrq); | 
|  | 1150 | goto unlock; | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1151 | } | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1152 |  | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1153 | prev_state = state = STATE_SENDING_DATA; | 
|  | 1154 | /* fall through */ | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1155 |  | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1156 | case STATE_SENDING_DATA: | 
|  | 1157 | if (atmci_test_and_clear_pending(host, | 
|  | 1158 | EVENT_DATA_ERROR)) { | 
| Haavard Skinnemoen | 65e8b08 | 2008-07-30 20:29:03 +0200 | [diff] [blame] | 1159 | atmci_stop_dma(host); | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1160 | if (data->stop) | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1161 | send_stop_cmd(host, data); | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1162 | state = STATE_DATA_ERROR; | 
|  | 1163 | break; | 
|  | 1164 | } | 
|  | 1165 |  | 
|  | 1166 | if (!atmci_test_and_clear_pending(host, | 
|  | 1167 | EVENT_XFER_COMPLETE)) | 
|  | 1168 | break; | 
|  | 1169 |  | 
|  | 1170 | atmci_set_completed(host, EVENT_XFER_COMPLETE); | 
|  | 1171 | prev_state = state = STATE_DATA_BUSY; | 
|  | 1172 | /* fall through */ | 
|  | 1173 |  | 
|  | 1174 | case STATE_DATA_BUSY: | 
|  | 1175 | if (!atmci_test_and_clear_pending(host, | 
|  | 1176 | EVENT_DATA_COMPLETE)) | 
|  | 1177 | break; | 
|  | 1178 |  | 
|  | 1179 | host->data = NULL; | 
|  | 1180 | atmci_set_completed(host, EVENT_DATA_COMPLETE); | 
|  | 1181 | status = host->data_status; | 
|  | 1182 | if (unlikely(status & ATMCI_DATA_ERROR_FLAGS)) { | 
|  | 1183 | if (status & MCI_DTOE) { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1184 | dev_dbg(&host->pdev->dev, | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1185 | "data timeout error\n"); | 
|  | 1186 | data->error = -ETIMEDOUT; | 
|  | 1187 | } else if (status & MCI_DCRCE) { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1188 | dev_dbg(&host->pdev->dev, | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1189 | "data CRC error\n"); | 
|  | 1190 | data->error = -EILSEQ; | 
|  | 1191 | } else { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1192 | dev_dbg(&host->pdev->dev, | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1193 | "data FIFO error (status=%08x)\n", | 
|  | 1194 | status); | 
|  | 1195 | data->error = -EIO; | 
|  | 1196 | } | 
|  | 1197 | } else { | 
|  | 1198 | data->bytes_xfered = data->blocks * data->blksz; | 
|  | 1199 | data->error = 0; | 
|  | 1200 | } | 
|  | 1201 |  | 
|  | 1202 | if (!data->stop) { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1203 | atmci_request_end(host, host->mrq); | 
|  | 1204 | goto unlock; | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1205 | } | 
|  | 1206 |  | 
|  | 1207 | prev_state = state = STATE_SENDING_STOP; | 
|  | 1208 | if (!data->error) | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1209 | send_stop_cmd(host, data); | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1210 | /* fall through */ | 
|  | 1211 |  | 
|  | 1212 | case STATE_SENDING_STOP: | 
|  | 1213 | if (!atmci_test_and_clear_pending(host, | 
|  | 1214 | EVENT_CMD_COMPLETE)) | 
|  | 1215 | break; | 
|  | 1216 |  | 
|  | 1217 | host->cmd = NULL; | 
|  | 1218 | atmci_command_complete(host, mrq->stop); | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1219 | atmci_request_end(host, host->mrq); | 
|  | 1220 | goto unlock; | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1221 |  | 
|  | 1222 | case STATE_DATA_ERROR: | 
|  | 1223 | if (!atmci_test_and_clear_pending(host, | 
|  | 1224 | EVENT_XFER_COMPLETE)) | 
|  | 1225 | break; | 
|  | 1226 |  | 
|  | 1227 | state = STATE_DATA_BUSY; | 
|  | 1228 | break; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1229 | } | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1230 | } while (state != prev_state); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1231 |  | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1232 | host->state = state; | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1233 |  | 
|  | 1234 | unlock: | 
|  | 1235 | spin_unlock(&host->lock); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1236 | } | 
|  | 1237 |  | 
|  | 1238 | static void atmci_read_data_pio(struct atmel_mci *host) | 
|  | 1239 | { | 
|  | 1240 | struct scatterlist	*sg = host->sg; | 
|  | 1241 | void			*buf = sg_virt(sg); | 
|  | 1242 | unsigned int		offset = host->pio_offset; | 
|  | 1243 | struct mmc_data		*data = host->data; | 
|  | 1244 | u32			value; | 
|  | 1245 | u32			status; | 
|  | 1246 | unsigned int		nbytes = 0; | 
|  | 1247 |  | 
|  | 1248 | do { | 
|  | 1249 | value = mci_readl(host, RDR); | 
|  | 1250 | if (likely(offset + 4 <= sg->length)) { | 
|  | 1251 | put_unaligned(value, (u32 *)(buf + offset)); | 
|  | 1252 |  | 
|  | 1253 | offset += 4; | 
|  | 1254 | nbytes += 4; | 
|  | 1255 |  | 
|  | 1256 | if (offset == sg->length) { | 
| Haavard Skinnemoen | 5e7184a | 2008-10-05 15:27:50 +0200 | [diff] [blame] | 1257 | flush_dcache_page(sg_page(sg)); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1258 | host->sg = sg = sg_next(sg); | 
|  | 1259 | if (!sg) | 
|  | 1260 | goto done; | 
|  | 1261 |  | 
|  | 1262 | offset = 0; | 
|  | 1263 | buf = sg_virt(sg); | 
|  | 1264 | } | 
|  | 1265 | } else { | 
|  | 1266 | unsigned int remaining = sg->length - offset; | 
|  | 1267 | memcpy(buf + offset, &value, remaining); | 
|  | 1268 | nbytes += remaining; | 
|  | 1269 |  | 
|  | 1270 | flush_dcache_page(sg_page(sg)); | 
|  | 1271 | host->sg = sg = sg_next(sg); | 
|  | 1272 | if (!sg) | 
|  | 1273 | goto done; | 
|  | 1274 |  | 
|  | 1275 | offset = 4 - remaining; | 
|  | 1276 | buf = sg_virt(sg); | 
|  | 1277 | memcpy(buf, (u8 *)&value + remaining, offset); | 
|  | 1278 | nbytes += offset; | 
|  | 1279 | } | 
|  | 1280 |  | 
|  | 1281 | status = mci_readl(host, SR); | 
|  | 1282 | if (status & ATMCI_DATA_ERROR_FLAGS) { | 
|  | 1283 | mci_writel(host, IDR, (MCI_NOTBUSY | MCI_RXRDY | 
|  | 1284 | | ATMCI_DATA_ERROR_FLAGS)); | 
|  | 1285 | host->data_status = status; | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1286 | data->bytes_xfered += nbytes; | 
|  | 1287 | smp_wmb(); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1288 | atmci_set_pending(host, EVENT_DATA_ERROR); | 
|  | 1289 | tasklet_schedule(&host->tasklet); | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1290 | return; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1291 | } | 
|  | 1292 | } while (status & MCI_RXRDY); | 
|  | 1293 |  | 
|  | 1294 | host->pio_offset = offset; | 
|  | 1295 | data->bytes_xfered += nbytes; | 
|  | 1296 |  | 
|  | 1297 | return; | 
|  | 1298 |  | 
|  | 1299 | done: | 
|  | 1300 | mci_writel(host, IDR, MCI_RXRDY); | 
|  | 1301 | mci_writel(host, IER, MCI_NOTBUSY); | 
|  | 1302 | data->bytes_xfered += nbytes; | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1303 | smp_wmb(); | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1304 | atmci_set_pending(host, EVENT_XFER_COMPLETE); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1305 | } | 
|  | 1306 |  | 
|  | 1307 | static void atmci_write_data_pio(struct atmel_mci *host) | 
|  | 1308 | { | 
|  | 1309 | struct scatterlist	*sg = host->sg; | 
|  | 1310 | void			*buf = sg_virt(sg); | 
|  | 1311 | unsigned int		offset = host->pio_offset; | 
|  | 1312 | struct mmc_data		*data = host->data; | 
|  | 1313 | u32			value; | 
|  | 1314 | u32			status; | 
|  | 1315 | unsigned int		nbytes = 0; | 
|  | 1316 |  | 
|  | 1317 | do { | 
|  | 1318 | if (likely(offset + 4 <= sg->length)) { | 
|  | 1319 | value = get_unaligned((u32 *)(buf + offset)); | 
|  | 1320 | mci_writel(host, TDR, value); | 
|  | 1321 |  | 
|  | 1322 | offset += 4; | 
|  | 1323 | nbytes += 4; | 
|  | 1324 | if (offset == sg->length) { | 
|  | 1325 | host->sg = sg = sg_next(sg); | 
|  | 1326 | if (!sg) | 
|  | 1327 | goto done; | 
|  | 1328 |  | 
|  | 1329 | offset = 0; | 
|  | 1330 | buf = sg_virt(sg); | 
|  | 1331 | } | 
|  | 1332 | } else { | 
|  | 1333 | unsigned int remaining = sg->length - offset; | 
|  | 1334 |  | 
|  | 1335 | value = 0; | 
|  | 1336 | memcpy(&value, buf + offset, remaining); | 
|  | 1337 | nbytes += remaining; | 
|  | 1338 |  | 
|  | 1339 | host->sg = sg = sg_next(sg); | 
|  | 1340 | if (!sg) { | 
|  | 1341 | mci_writel(host, TDR, value); | 
|  | 1342 | goto done; | 
|  | 1343 | } | 
|  | 1344 |  | 
|  | 1345 | offset = 4 - remaining; | 
|  | 1346 | buf = sg_virt(sg); | 
|  | 1347 | memcpy((u8 *)&value + remaining, buf, offset); | 
|  | 1348 | mci_writel(host, TDR, value); | 
|  | 1349 | nbytes += offset; | 
|  | 1350 | } | 
|  | 1351 |  | 
|  | 1352 | status = mci_readl(host, SR); | 
|  | 1353 | if (status & ATMCI_DATA_ERROR_FLAGS) { | 
|  | 1354 | mci_writel(host, IDR, (MCI_NOTBUSY | MCI_TXRDY | 
|  | 1355 | | ATMCI_DATA_ERROR_FLAGS)); | 
|  | 1356 | host->data_status = status; | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1357 | data->bytes_xfered += nbytes; | 
|  | 1358 | smp_wmb(); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1359 | atmci_set_pending(host, EVENT_DATA_ERROR); | 
|  | 1360 | tasklet_schedule(&host->tasklet); | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1361 | return; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1362 | } | 
|  | 1363 | } while (status & MCI_TXRDY); | 
|  | 1364 |  | 
|  | 1365 | host->pio_offset = offset; | 
|  | 1366 | data->bytes_xfered += nbytes; | 
|  | 1367 |  | 
|  | 1368 | return; | 
|  | 1369 |  | 
|  | 1370 | done: | 
|  | 1371 | mci_writel(host, IDR, MCI_TXRDY); | 
|  | 1372 | mci_writel(host, IER, MCI_NOTBUSY); | 
|  | 1373 | data->bytes_xfered += nbytes; | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1374 | smp_wmb(); | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1375 | atmci_set_pending(host, EVENT_XFER_COMPLETE); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1376 | } | 
|  | 1377 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1378 | static void atmci_cmd_interrupt(struct atmel_mci *host, u32 status) | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1379 | { | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1380 | mci_writel(host, IDR, MCI_CMDRDY); | 
|  | 1381 |  | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1382 | host->cmd_status = status; | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1383 | smp_wmb(); | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1384 | atmci_set_pending(host, EVENT_CMD_COMPLETE); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1385 | tasklet_schedule(&host->tasklet); | 
|  | 1386 | } | 
|  | 1387 |  | 
|  | 1388 | static irqreturn_t atmci_interrupt(int irq, void *dev_id) | 
|  | 1389 | { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1390 | struct atmel_mci	*host = dev_id; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1391 | u32			status, mask, pending; | 
|  | 1392 | unsigned int		pass_count = 0; | 
|  | 1393 |  | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1394 | do { | 
|  | 1395 | status = mci_readl(host, SR); | 
|  | 1396 | mask = mci_readl(host, IMR); | 
|  | 1397 | pending = status & mask; | 
|  | 1398 | if (!pending) | 
|  | 1399 | break; | 
|  | 1400 |  | 
|  | 1401 | if (pending & ATMCI_DATA_ERROR_FLAGS) { | 
|  | 1402 | mci_writel(host, IDR, ATMCI_DATA_ERROR_FLAGS | 
|  | 1403 | | MCI_RXRDY | MCI_TXRDY); | 
|  | 1404 | pending &= mci_readl(host, IMR); | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1405 |  | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1406 | host->data_status = status; | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1407 | smp_wmb(); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1408 | atmci_set_pending(host, EVENT_DATA_ERROR); | 
|  | 1409 | tasklet_schedule(&host->tasklet); | 
|  | 1410 | } | 
|  | 1411 | if (pending & MCI_NOTBUSY) { | 
| Haavard Skinnemoen | c06ad25 | 2008-07-31 14:49:16 +0200 | [diff] [blame] | 1412 | mci_writel(host, IDR, | 
|  | 1413 | ATMCI_DATA_ERROR_FLAGS | MCI_NOTBUSY); | 
| Haavard Skinnemoen | ca55f46 | 2008-10-05 15:16:59 +0200 | [diff] [blame] | 1414 | if (!host->data_status) | 
|  | 1415 | host->data_status = status; | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1416 | smp_wmb(); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1417 | atmci_set_pending(host, EVENT_DATA_COMPLETE); | 
|  | 1418 | tasklet_schedule(&host->tasklet); | 
|  | 1419 | } | 
|  | 1420 | if (pending & MCI_RXRDY) | 
|  | 1421 | atmci_read_data_pio(host); | 
|  | 1422 | if (pending & MCI_TXRDY) | 
|  | 1423 | atmci_write_data_pio(host); | 
|  | 1424 |  | 
|  | 1425 | if (pending & MCI_CMDRDY) | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1426 | atmci_cmd_interrupt(host, status); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1427 | } while (pass_count++ < 5); | 
|  | 1428 |  | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1429 | return pass_count ? IRQ_HANDLED : IRQ_NONE; | 
|  | 1430 | } | 
|  | 1431 |  | 
|  | 1432 | static irqreturn_t atmci_detect_interrupt(int irq, void *dev_id) | 
|  | 1433 | { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1434 | struct atmel_mci_slot	*slot = dev_id; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1435 |  | 
|  | 1436 | /* | 
|  | 1437 | * Disable interrupts until the pin has stabilized and check | 
|  | 1438 | * the state then. Use mod_timer() since we may be in the | 
|  | 1439 | * middle of the timer routine when this interrupt triggers. | 
|  | 1440 | */ | 
|  | 1441 | disable_irq_nosync(irq); | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1442 | mod_timer(&slot->detect_timer, jiffies + msecs_to_jiffies(20)); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1443 |  | 
|  | 1444 | return IRQ_HANDLED; | 
|  | 1445 | } | 
|  | 1446 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1447 | static int __init atmci_init_slot(struct atmel_mci *host, | 
|  | 1448 | struct mci_slot_pdata *slot_data, unsigned int id, | 
|  | 1449 | u32 sdc_reg) | 
|  | 1450 | { | 
|  | 1451 | struct mmc_host			*mmc; | 
|  | 1452 | struct atmel_mci_slot		*slot; | 
|  | 1453 |  | 
|  | 1454 | mmc = mmc_alloc_host(sizeof(struct atmel_mci_slot), &host->pdev->dev); | 
|  | 1455 | if (!mmc) | 
|  | 1456 | return -ENOMEM; | 
|  | 1457 |  | 
|  | 1458 | slot = mmc_priv(mmc); | 
|  | 1459 | slot->mmc = mmc; | 
|  | 1460 | slot->host = host; | 
|  | 1461 | slot->detect_pin = slot_data->detect_pin; | 
|  | 1462 | slot->wp_pin = slot_data->wp_pin; | 
| Jonas Larsson | 1c1452b | 2009-03-31 11:16:48 +0200 | [diff] [blame] | 1463 | slot->detect_is_active_high = slot_data->detect_is_active_high; | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1464 | slot->sdc_reg = sdc_reg; | 
|  | 1465 |  | 
|  | 1466 | mmc->ops = &atmci_ops; | 
|  | 1467 | mmc->f_min = DIV_ROUND_UP(host->bus_hz, 512); | 
|  | 1468 | mmc->f_max = host->bus_hz / 2; | 
|  | 1469 | mmc->ocr_avail	= MMC_VDD_32_33 | MMC_VDD_33_34; | 
|  | 1470 | if (slot_data->bus_width >= 4) | 
|  | 1471 | mmc->caps |= MMC_CAP_4_BIT_DATA; | 
|  | 1472 |  | 
|  | 1473 | mmc->max_hw_segs = 64; | 
|  | 1474 | mmc->max_phys_segs = 64; | 
|  | 1475 | mmc->max_req_size = 32768 * 512; | 
|  | 1476 | mmc->max_blk_size = 32768; | 
|  | 1477 | mmc->max_blk_count = 512; | 
|  | 1478 |  | 
|  | 1479 | /* Assume card is present initially */ | 
|  | 1480 | set_bit(ATMCI_CARD_PRESENT, &slot->flags); | 
|  | 1481 | if (gpio_is_valid(slot->detect_pin)) { | 
|  | 1482 | if (gpio_request(slot->detect_pin, "mmc_detect")) { | 
|  | 1483 | dev_dbg(&mmc->class_dev, "no detect pin available\n"); | 
|  | 1484 | slot->detect_pin = -EBUSY; | 
| Jonas Larsson | 1c1452b | 2009-03-31 11:16:48 +0200 | [diff] [blame] | 1485 | } else if (gpio_get_value(slot->detect_pin) ^ | 
|  | 1486 | slot->detect_is_active_high) { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1487 | clear_bit(ATMCI_CARD_PRESENT, &slot->flags); | 
|  | 1488 | } | 
|  | 1489 | } | 
|  | 1490 |  | 
|  | 1491 | if (!gpio_is_valid(slot->detect_pin)) | 
|  | 1492 | mmc->caps |= MMC_CAP_NEEDS_POLL; | 
|  | 1493 |  | 
|  | 1494 | if (gpio_is_valid(slot->wp_pin)) { | 
|  | 1495 | if (gpio_request(slot->wp_pin, "mmc_wp")) { | 
|  | 1496 | dev_dbg(&mmc->class_dev, "no WP pin available\n"); | 
|  | 1497 | slot->wp_pin = -EBUSY; | 
|  | 1498 | } | 
|  | 1499 | } | 
|  | 1500 |  | 
|  | 1501 | host->slot[id] = slot; | 
|  | 1502 | mmc_add_host(mmc); | 
|  | 1503 |  | 
|  | 1504 | if (gpio_is_valid(slot->detect_pin)) { | 
|  | 1505 | int ret; | 
|  | 1506 |  | 
|  | 1507 | setup_timer(&slot->detect_timer, atmci_detect_change, | 
|  | 1508 | (unsigned long)slot); | 
|  | 1509 |  | 
|  | 1510 | ret = request_irq(gpio_to_irq(slot->detect_pin), | 
|  | 1511 | atmci_detect_interrupt, | 
|  | 1512 | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, | 
|  | 1513 | "mmc-detect", slot); | 
|  | 1514 | if (ret) { | 
|  | 1515 | dev_dbg(&mmc->class_dev, | 
|  | 1516 | "could not request IRQ %d for detect pin\n", | 
|  | 1517 | gpio_to_irq(slot->detect_pin)); | 
|  | 1518 | gpio_free(slot->detect_pin); | 
|  | 1519 | slot->detect_pin = -EBUSY; | 
|  | 1520 | } | 
|  | 1521 | } | 
|  | 1522 |  | 
|  | 1523 | atmci_init_debugfs(slot); | 
|  | 1524 |  | 
|  | 1525 | return 0; | 
|  | 1526 | } | 
|  | 1527 |  | 
|  | 1528 | static void __exit atmci_cleanup_slot(struct atmel_mci_slot *slot, | 
|  | 1529 | unsigned int id) | 
|  | 1530 | { | 
|  | 1531 | /* Debugfs stuff is cleaned up by mmc core */ | 
|  | 1532 |  | 
|  | 1533 | set_bit(ATMCI_SHUTDOWN, &slot->flags); | 
|  | 1534 | smp_wmb(); | 
|  | 1535 |  | 
|  | 1536 | mmc_remove_host(slot->mmc); | 
|  | 1537 |  | 
|  | 1538 | if (gpio_is_valid(slot->detect_pin)) { | 
|  | 1539 | int pin = slot->detect_pin; | 
|  | 1540 |  | 
|  | 1541 | free_irq(gpio_to_irq(pin), slot); | 
|  | 1542 | del_timer_sync(&slot->detect_timer); | 
|  | 1543 | gpio_free(pin); | 
|  | 1544 | } | 
|  | 1545 | if (gpio_is_valid(slot->wp_pin)) | 
|  | 1546 | gpio_free(slot->wp_pin); | 
|  | 1547 |  | 
|  | 1548 | slot->host->slot[id] = NULL; | 
|  | 1549 | mmc_free_host(slot->mmc); | 
|  | 1550 | } | 
|  | 1551 |  | 
| Dan Williams | 74465b4 | 2009-01-06 11:38:16 -0700 | [diff] [blame] | 1552 | #ifdef CONFIG_MMC_ATMELMCI_DMA | 
| Dan Williams | 7dd6025 | 2009-01-06 11:38:19 -0700 | [diff] [blame] | 1553 | static bool filter(struct dma_chan *chan, void *slave) | 
| Dan Williams | 74465b4 | 2009-01-06 11:38:16 -0700 | [diff] [blame] | 1554 | { | 
|  | 1555 | struct dw_dma_slave *dws = slave; | 
|  | 1556 |  | 
| Dan Williams | 287d859 | 2009-02-18 14:48:26 -0800 | [diff] [blame] | 1557 | if (dws->dma_dev == chan->device->dev) { | 
|  | 1558 | chan->private = dws; | 
| Dan Williams | 7dd6025 | 2009-01-06 11:38:19 -0700 | [diff] [blame] | 1559 | return true; | 
| Dan Williams | 287d859 | 2009-02-18 14:48:26 -0800 | [diff] [blame] | 1560 | } else | 
| Dan Williams | 7dd6025 | 2009-01-06 11:38:19 -0700 | [diff] [blame] | 1561 | return false; | 
| Dan Williams | 74465b4 | 2009-01-06 11:38:16 -0700 | [diff] [blame] | 1562 | } | 
|  | 1563 | #endif | 
|  | 1564 |  | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1565 | static int __init atmci_probe(struct platform_device *pdev) | 
|  | 1566 | { | 
|  | 1567 | struct mci_platform_data	*pdata; | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1568 | struct atmel_mci		*host; | 
|  | 1569 | struct resource			*regs; | 
|  | 1570 | unsigned int			nr_slots; | 
|  | 1571 | int				irq; | 
|  | 1572 | int				ret; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1573 |  | 
|  | 1574 | regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
|  | 1575 | if (!regs) | 
|  | 1576 | return -ENXIO; | 
|  | 1577 | pdata = pdev->dev.platform_data; | 
|  | 1578 | if (!pdata) | 
|  | 1579 | return -ENXIO; | 
|  | 1580 | irq = platform_get_irq(pdev, 0); | 
|  | 1581 | if (irq < 0) | 
|  | 1582 | return irq; | 
|  | 1583 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1584 | host = kzalloc(sizeof(struct atmel_mci), GFP_KERNEL); | 
|  | 1585 | if (!host) | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1586 | return -ENOMEM; | 
|  | 1587 |  | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1588 | host->pdev = pdev; | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1589 | spin_lock_init(&host->lock); | 
|  | 1590 | INIT_LIST_HEAD(&host->queue); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1591 |  | 
|  | 1592 | host->mck = clk_get(&pdev->dev, "mci_clk"); | 
|  | 1593 | if (IS_ERR(host->mck)) { | 
|  | 1594 | ret = PTR_ERR(host->mck); | 
|  | 1595 | goto err_clk_get; | 
|  | 1596 | } | 
|  | 1597 |  | 
|  | 1598 | ret = -ENOMEM; | 
|  | 1599 | host->regs = ioremap(regs->start, regs->end - regs->start + 1); | 
|  | 1600 | if (!host->regs) | 
|  | 1601 | goto err_ioremap; | 
|  | 1602 |  | 
|  | 1603 | clk_enable(host->mck); | 
|  | 1604 | mci_writel(host, CR, MCI_CR_SWRST); | 
|  | 1605 | host->bus_hz = clk_get_rate(host->mck); | 
|  | 1606 | clk_disable(host->mck); | 
|  | 1607 |  | 
|  | 1608 | host->mapbase = regs->start; | 
|  | 1609 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1610 | tasklet_init(&host->tasklet, atmci_tasklet_func, (unsigned long)host); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1611 |  | 
| Kay Sievers | 89c8aa2 | 2009-02-02 21:08:30 +0100 | [diff] [blame] | 1612 | ret = request_irq(irq, atmci_interrupt, 0, dev_name(&pdev->dev), host); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1613 | if (ret) | 
|  | 1614 | goto err_request_irq; | 
|  | 1615 |  | 
| Haavard Skinnemoen | 65e8b08 | 2008-07-30 20:29:03 +0200 | [diff] [blame] | 1616 | #ifdef CONFIG_MMC_ATMELMCI_DMA | 
| Dan Williams | 74465b4 | 2009-01-06 11:38:16 -0700 | [diff] [blame] | 1617 | if (pdata->dma_slave.dma_dev) { | 
|  | 1618 | struct dw_dma_slave *dws = &pdata->dma_slave; | 
|  | 1619 | dma_cap_mask_t mask; | 
| Haavard Skinnemoen | 65e8b08 | 2008-07-30 20:29:03 +0200 | [diff] [blame] | 1620 |  | 
| Dan Williams | 74465b4 | 2009-01-06 11:38:16 -0700 | [diff] [blame] | 1621 | dws->tx_reg = regs->start + MCI_TDR; | 
|  | 1622 | dws->rx_reg = regs->start + MCI_RDR; | 
| Haavard Skinnemoen | 65e8b08 | 2008-07-30 20:29:03 +0200 | [diff] [blame] | 1623 |  | 
|  | 1624 | /* Try to grab a DMA channel */ | 
| Dan Williams | 74465b4 | 2009-01-06 11:38:16 -0700 | [diff] [blame] | 1625 | dma_cap_zero(mask); | 
|  | 1626 | dma_cap_set(DMA_SLAVE, mask); | 
|  | 1627 | host->dma.chan = dma_request_channel(mask, filter, dws); | 
| Haavard Skinnemoen | 65e8b08 | 2008-07-30 20:29:03 +0200 | [diff] [blame] | 1628 | } | 
| Dan Williams | 74465b4 | 2009-01-06 11:38:16 -0700 | [diff] [blame] | 1629 | if (!host->dma.chan) | 
|  | 1630 | dev_notice(&pdev->dev, "DMA not available, using PIO\n"); | 
| Haavard Skinnemoen | 65e8b08 | 2008-07-30 20:29:03 +0200 | [diff] [blame] | 1631 | #endif /* CONFIG_MMC_ATMELMCI_DMA */ | 
|  | 1632 |  | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1633 | platform_set_drvdata(pdev, host); | 
|  | 1634 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1635 | /* We need at least one slot to succeed */ | 
|  | 1636 | nr_slots = 0; | 
|  | 1637 | ret = -ENODEV; | 
|  | 1638 | if (pdata->slot[0].bus_width) { | 
|  | 1639 | ret = atmci_init_slot(host, &pdata->slot[0], | 
|  | 1640 | MCI_SDCSEL_SLOT_A, 0); | 
|  | 1641 | if (!ret) | 
|  | 1642 | nr_slots++; | 
|  | 1643 | } | 
|  | 1644 | if (pdata->slot[1].bus_width) { | 
|  | 1645 | ret = atmci_init_slot(host, &pdata->slot[1], | 
|  | 1646 | MCI_SDCSEL_SLOT_B, 1); | 
|  | 1647 | if (!ret) | 
|  | 1648 | nr_slots++; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1649 | } | 
|  | 1650 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1651 | if (!nr_slots) | 
|  | 1652 | goto err_init_slot; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1653 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1654 | dev_info(&pdev->dev, | 
|  | 1655 | "Atmel MCI controller at 0x%08lx irq %d, %u slots\n", | 
|  | 1656 | host->mapbase, irq, nr_slots); | 
| Haavard Skinnemoen | deec9ae | 2008-07-24 14:18:59 +0200 | [diff] [blame] | 1657 |  | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1658 | return 0; | 
|  | 1659 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1660 | err_init_slot: | 
| Haavard Skinnemoen | 65e8b08 | 2008-07-30 20:29:03 +0200 | [diff] [blame] | 1661 | #ifdef CONFIG_MMC_ATMELMCI_DMA | 
| Dan Williams | 74465b4 | 2009-01-06 11:38:16 -0700 | [diff] [blame] | 1662 | if (host->dma.chan) | 
|  | 1663 | dma_release_channel(host->dma.chan); | 
| Haavard Skinnemoen | 65e8b08 | 2008-07-30 20:29:03 +0200 | [diff] [blame] | 1664 | #endif | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1665 | free_irq(irq, host); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1666 | err_request_irq: | 
|  | 1667 | iounmap(host->regs); | 
|  | 1668 | err_ioremap: | 
|  | 1669 | clk_put(host->mck); | 
|  | 1670 | err_clk_get: | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1671 | kfree(host); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1672 | return ret; | 
|  | 1673 | } | 
|  | 1674 |  | 
|  | 1675 | static int __exit atmci_remove(struct platform_device *pdev) | 
|  | 1676 | { | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1677 | struct atmel_mci	*host = platform_get_drvdata(pdev); | 
|  | 1678 | unsigned int		i; | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1679 |  | 
|  | 1680 | platform_set_drvdata(pdev, NULL); | 
|  | 1681 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1682 | for (i = 0; i < ATMEL_MCI_MAX_NR_SLOTS; i++) { | 
|  | 1683 | if (host->slot[i]) | 
|  | 1684 | atmci_cleanup_slot(host->slot[i], i); | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1685 | } | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1686 |  | 
|  | 1687 | clk_enable(host->mck); | 
|  | 1688 | mci_writel(host, IDR, ~0UL); | 
|  | 1689 | mci_writel(host, CR, MCI_CR_MCIDIS); | 
|  | 1690 | mci_readl(host, SR); | 
|  | 1691 | clk_disable(host->mck); | 
|  | 1692 |  | 
| Haavard Skinnemoen | 65e8b08 | 2008-07-30 20:29:03 +0200 | [diff] [blame] | 1693 | #ifdef CONFIG_MMC_ATMELMCI_DMA | 
| Dan Williams | 74465b4 | 2009-01-06 11:38:16 -0700 | [diff] [blame] | 1694 | if (host->dma.chan) | 
|  | 1695 | dma_release_channel(host->dma.chan); | 
| Haavard Skinnemoen | 65e8b08 | 2008-07-30 20:29:03 +0200 | [diff] [blame] | 1696 | #endif | 
|  | 1697 |  | 
| Haavard Skinnemoen | 965ebf3 | 2008-09-17 20:53:55 +0200 | [diff] [blame] | 1698 | free_irq(platform_get_irq(pdev, 0), host); | 
|  | 1699 | iounmap(host->regs); | 
|  | 1700 |  | 
|  | 1701 | clk_put(host->mck); | 
|  | 1702 | kfree(host); | 
|  | 1703 |  | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1704 | return 0; | 
|  | 1705 | } | 
|  | 1706 |  | 
|  | 1707 | static struct platform_driver atmci_driver = { | 
|  | 1708 | .remove		= __exit_p(atmci_remove), | 
|  | 1709 | .driver		= { | 
|  | 1710 | .name		= "atmel_mci", | 
|  | 1711 | }, | 
|  | 1712 | }; | 
|  | 1713 |  | 
|  | 1714 | static int __init atmci_init(void) | 
|  | 1715 | { | 
|  | 1716 | return platform_driver_probe(&atmci_driver, atmci_probe); | 
|  | 1717 | } | 
|  | 1718 |  | 
|  | 1719 | static void __exit atmci_exit(void) | 
|  | 1720 | { | 
|  | 1721 | platform_driver_unregister(&atmci_driver); | 
|  | 1722 | } | 
|  | 1723 |  | 
| Dan Williams | 74465b4 | 2009-01-06 11:38:16 -0700 | [diff] [blame] | 1724 | late_initcall(atmci_init); /* try to load after dma driver when built-in */ | 
| Haavard Skinnemoen | 7d2be07 | 2008-06-30 18:35:03 +0200 | [diff] [blame] | 1725 | module_exit(atmci_exit); | 
|  | 1726 |  | 
|  | 1727 | MODULE_DESCRIPTION("Atmel Multimedia Card Interface driver"); | 
|  | 1728 | MODULE_AUTHOR("Haavard Skinnemoen <haavard.skinnemoen@atmel.com>"); | 
|  | 1729 | MODULE_LICENSE("GPL v2"); |