aboutsummaryrefslogblamecommitdiffstats
path: root/drivers/scsi/gdth_ioctl.h
blob: 783fae737f177e5082cb19be73da87e556d2e0b5 (plain) (tree)


























































































































































































































































































































































                                                                                
#ifndef _GDTH_IOCTL_H
#define _GDTH_IOCTL_H

/* gdth_ioctl.h
 * $Id: gdth_ioctl.h,v 1.14 2004/02/19 15:43:15 achim Exp $
 */

/* IOCTLs */
#define GDTIOCTL_MASK       ('J'<<8)
#define GDTIOCTL_GENERAL    (GDTIOCTL_MASK | 0) /* general IOCTL */
#define GDTIOCTL_DRVERS     (GDTIOCTL_MASK | 1) /* get driver version */
#define GDTIOCTL_CTRTYPE    (GDTIOCTL_MASK | 2) /* get controller type */
#define GDTIOCTL_OSVERS     (GDTIOCTL_MASK | 3) /* get OS version */
#define GDTIOCTL_HDRLIST    (GDTIOCTL_MASK | 4) /* get host drive list */
#define GDTIOCTL_CTRCNT     (GDTIOCTL_MASK | 5) /* get controller count */
#define GDTIOCTL_LOCKDRV    (GDTIOCTL_MASK | 6) /* lock host drive */
#define GDTIOCTL_LOCKCHN    (GDTIOCTL_MASK | 7) /* lock channel */
#define GDTIOCTL_EVENT      (GDTIOCTL_MASK | 8) /* read controller events */
#define GDTIOCTL_SCSI       (GDTIOCTL_MASK | 9) /* SCSI command */
#define GDTIOCTL_RESET_BUS  (GDTIOCTL_MASK |10) /* reset SCSI bus */
#define GDTIOCTL_RESCAN     (GDTIOCTL_MASK |11) /* rescan host drives */
#define GDTIOCTL_RESET_DRV  (GDTIOCTL_MASK |12) /* reset (remote) drv. res. */

#define GDTIOCTL_MAGIC  0xaffe0004
#define EVENT_SIZE      294 
#define GDTH_MAXSG      32                      /* max. s/g elements */

#define MAX_LDRIVES     255                     /* max. log. drive count */
#ifdef GDTH_IOCTL_PROC
#define MAX_HDRIVES     100                     /* max. host drive count */
#else
#define MAX_HDRIVES     MAX_LDRIVES             /* max. host drive count */
#endif

/* typedefs */
#ifdef __KERNEL__
typedef u32     ulong32;
typedef u64     ulong64;
#endif

#define PACKED  __attribute__((packed))

/* scatter/gather element */
typedef struct {
    ulong32     sg_ptr;                         /* address */
    ulong32     sg_len;                         /* length */
} PACKED gdth_sg_str;

/* scatter/gather element - 64bit addresses */
typedef struct {
    ulong64     sg_ptr;                         /* address */
    ulong32     sg_len;                         /* length */
} PACKED gdth_sg64_str;

/* command structure */
typedef struct {
    ulong32     BoardNode;                      /* board node (always 0) */
    ulong32     CommandIndex;                   /* command number */
    ushort      OpCode;                         /* the command (READ,..) */
    union {
        struct {
            ushort      DeviceNo;               /* number of cache drive */
            ulong32     BlockNo;                /* block number */
            ulong32     BlockCnt;               /* block count */
            ulong32     DestAddr;               /* dest. addr. (if s/g: -1) */
            ulong32     sg_canz;                /* s/g element count */
            gdth_sg_str sg_lst[GDTH_MAXSG];     /* s/g list */
        } PACKED cache;                         /* cache service cmd. str. */
        struct {
            ushort      DeviceNo;               /* number of cache drive */
            ulong64     BlockNo;                /* block number */
            ulong32     BlockCnt;               /* block count */
            ulong64     DestAddr;               /* dest. addr. (if s/g: -1) */
            ulong32     sg_canz;                /* s/g element count */
            gdth_sg64_str sg_lst[GDTH_MAXSG];   /* s/g list */
        } PACKED cache64;                       /* cache service cmd. str. */
        struct {
            ushort      param_size;             /* size of p_param buffer */
            ulong32     subfunc;                /* IOCTL function */
            ulong32     channel;                /* device */
            ulong64     p_param;                /* buffer */
        } PACKED ioctl;                         /* IOCTL command structure */
        struct {
            ushort      reserved;
            union {
                struct {
                    ulong32  msg_handle;        /* message handle */
                    ulong64  msg_addr;          /* message buffer address */
                } PACKED msg;
                unchar       data[12];          /* buffer for rtc data, ... */
            } su;
        } PACKED screen;                        /* screen service cmd. str. */
        struct {
            ushort      reserved;
            ulong32     direction;              /* data direction */
            ulong32     mdisc_time;             /* disc. time (0: no timeout)*/
            ulong32     mcon_time;              /* connect time(0: no to.) */
            ulong32     sdata;                  /* dest. addr. (if s/g: -1) */
            ulong32     sdlen;                  /* data length (bytes) */
            ulong32     clen;                   /* SCSI cmd. length(6,10,12) */
            unchar      cmd[12];                /* SCSI command */
            unchar      target;                 /* target ID */
            unchar      lun;                    /* LUN */
            unchar      bus;                    /* SCSI bus number */
            unchar      priority;               /* only 0 used */
            ulong32     sense_len;              /* sense data length */
            ulong32     sense_data;             /* sense data addr. */
            ulong32     link_p;                 /* linked cmds (not supp.) */
            ulong32     sg_ranz;                /* s/g element count */
            gdth_sg_str sg_lst[GDTH_MAXSG];     /* s/g list */
        } PACKED raw;                           /* raw service cmd. struct. */
        struct {
            ushort      reserved;
            ulong32     direction;              /* data direction */
            ulong32     mdisc_time;             /* disc. time (0: no timeout)*/
            ulong32     mcon_time;              /* connect time(0: no to.) */
            ulong64     sdata;                  /* dest. addr. (if s/g: -1) */
            ulong32     sdlen;                  /* data length (bytes) */
            ulong32     clen;                   /* SCSI cmd. length(6,..,16) */
            unchar      cmd[16];                /* SCSI command */
            unchar      target;                 /* target ID */
            unchar      lun;                    /* LUN */
            unchar      bus;                    /* SCSI bus number */
            unchar      priority;               /* only 0 used */
            ulong32     sense_len;              /* sense data length */
            ulong64     sense_data;             /* sense data addr. */
            ulong32     sg_ranz;                /* s/g element count */
            gdth_sg64_str sg_lst[GDTH_MAXSG];   /* s/g list */
        } PACKED raw64;                         /* raw service cmd. struct. */
    } u;
    /* additional variables */
    unchar      Service;                        /* controller service */
    unchar      reserved;
    ushort      Status;                         /* command result */
    ulong32     Info;                           /* additional information */
    void        *RequestBuffer;                 /* request buffer */
} PACKED gdth_cmd_str;

/* controller event structure */
#define ES_ASYNC    1
#define ES_DRIVER   2
#define ES_TEST     3
#define ES_SYNC     4
typedef struct {
    ushort                  size;               /* size of structure */
    union {
        char                stream[16];
        struct {
            ushort          ionode;
            ushort          service;
            ulong32         index;
        } PACKED driver;
        struct {
            ushort          ionode;
            ushort          service;
            ushort          status;
            ulong32         info;
            unchar          scsi_coord[3];
        } PACKED async;
        struct {
            ushort          ionode;
            ushort          service;
            ushort          status;
            ulong32         info;
            ushort          hostdrive;
            unchar          scsi_coord[3];
            unchar          sense_key;
        } PACKED sync;
        struct {
            ulong32         l1, l2, l3, l4;
        } PACKED test;
    } eu;
    ulong32                 severity;
    unchar                  event_string[256];          
} PACKED gdth_evt_data;

typedef struct {
    ulong32         first_stamp;
    ulong32         last_stamp;
    ushort          same_count;
    ushort          event_source;
    ushort          event_idx;
    unchar          application;
    unchar          reserved;
    gdth_evt_data   event_data;
} PACKED gdth_evt_str;


#ifdef GDTH_IOCTL_PROC
/* IOCTL structure (write) */
typedef struct {
    ulong32                 magic;              /* IOCTL magic */
    ushort                  ioctl;              /* IOCTL */
    ushort                  ionode;             /* controller number */
    ushort                  service;            /* controller service */
    ushort                  timeout;            /* timeout */
    union {
        struct {
            unchar          command[512];       /* controller command */
            unchar          data[1];            /* add. data */
        } general;
        struct {
            unchar          lock;               /* lock/unlock */
            unchar          drive_cnt;          /* drive count */
            ushort          drives[MAX_HDRIVES];/* drives */
        } lockdrv;
        struct {
            unchar          lock;               /* lock/unlock */
            unchar          channel;            /* channel */
        } lockchn;
        struct {
            int             erase;              /* erase event ? */
            int             handle;
            unchar          evt[EVENT_SIZE];    /* event structure */
        } event;
        struct {
            unchar          bus;                /* SCSI bus */
            unchar          target;             /* target ID */
            unchar          lun;                /* LUN */
            unchar          cmd_len;            /* command length */
            unchar          cmd[12];            /* SCSI command */
        } scsi;
        struct {
            ushort          hdr_no;             /* host drive number */
            unchar          flag;               /* old meth./add/remove */
        } rescan;
    } iu;
} gdth_iowr_str;

/* IOCTL structure (read) */
typedef struct {
    ulong32                 size;               /* buffer size */
    ulong32                 status;             /* IOCTL error code */
    union {
        struct {
            unchar          data[1];            /* data */
        } general;
        struct {
            ushort          version;            /* driver version */
        } drvers;
        struct {
            unchar          type;               /* controller type */
            ushort          info;               /* slot etc. */
            ushort          oem_id;             /* OEM ID */
            ushort          bios_ver;           /* not used */
            ushort          access;             /* not used */
            ushort          ext_type;           /* extended type */
            ushort          device_id;          /* device ID */
            ushort          sub_device_id;      /* sub device ID */
        } ctrtype;
        struct {
            unchar          version;            /* OS version */
            unchar          subversion;         /* OS subversion */
            ushort          revision;           /* revision */
        } osvers;
        struct {
            ushort          count;              /* controller count */
        } ctrcnt;
        struct {
            int             handle;
            unchar          evt[EVENT_SIZE];    /* event structure */
        } event;
        struct {
            unchar          bus;                /* SCSI bus, 0xff: invalid */
            unchar          target;             /* target ID */
            unchar          lun;                /* LUN */
            unchar          cluster_type;       /* cluster properties */
        } hdr_list[MAX_HDRIVES];                /* index is host drive number */
    } iu;
} gdth_iord_str;
#endif

/* GDTIOCTL_GENERAL */
typedef struct {
    ushort ionode;                              /* controller number */
    ushort timeout;                             /* timeout */
    ulong32 info;                               /* error info */ 
    ushort status;                              /* status */
    ulong data_len;                             /* data buffer size */
    ulong sense_len;                            /* sense buffer size */
    gdth_cmd_str command;                       /* command */                   
} gdth_ioctl_general;

/* GDTIOCTL_LOCKDRV */
typedef struct {
    ushort ionode;                              /* controller number */
    unchar lock;                                /* lock/unlock */
    unchar drive_cnt;                           /* drive count */
    ushort drives[MAX_HDRIVES];                 /* drives */
} gdth_ioctl_lockdrv;

/* GDTIOCTL_LOCKCHN */
typedef struct {
    ushort ionode;                              /* controller number */
    unchar lock;                                /* lock/unlock */
    unchar channel;                             /* channel */
} gdth_ioctl_lockchn;

/* GDTIOCTL_OSVERS */
typedef struct {
    unchar version;                             /* OS version */
    unchar subversion;                          /* OS subversion */
    ushort revision;                            /* revision */
} gdth_ioctl_osvers;

/* GDTIOCTL_CTRTYPE */
typedef struct {
    ushort ionode;                              /* controller number */
    unchar type;                                /* controller type */
    ushort info;                                /* slot etc. */
    ushort oem_id;                              /* OEM ID */
    ushort bios_ver;                            /* not used */
    ushort access;                              /* not used */
    ushort ext_type;                            /* extended type */
    ushort device_id;                           /* device ID */
    ushort sub_device_id;                       /* sub device ID */
} gdth_ioctl_ctrtype;

/* GDTIOCTL_EVENT */
typedef struct {
    ushort ionode;
    int erase;                                  /* erase event? */
    int handle;                                 /* event handle */
    gdth_evt_str event;
} gdth_ioctl_event;

/* GDTIOCTL_RESCAN/GDTIOCTL_HDRLIST */
typedef struct {
    ushort ionode;                              /* controller number */
    unchar flag;                                /* add/remove */
    ushort hdr_no;                              /* drive no. */
    struct {
        unchar bus;                             /* SCSI bus */
        unchar target;                          /* target ID */
        unchar lun;                             /* LUN */
        unchar cluster_type;                    /* cluster properties */
    } hdr_list[MAX_HDRIVES];                    /* index is host drive number */
} gdth_ioctl_rescan;

/* GDTIOCTL_RESET_BUS/GDTIOCTL_RESET_DRV */
typedef struct {
    ushort ionode;                              /* controller number */
    ushort number;                              /* bus/host drive number */
    ushort status;                              /* status */
} gdth_ioctl_reset;

#endif
d='n158' href='#n158'>158 159 160 161 162 163 164 165 166 167 168 169 170 171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607

















                                                                       


                         
                                  







                               







                              



























                                           










































                                                                              
                                









                                          
                                                     
 


                                                                           













































                                                                                                   

                                                              
                                                        

                                          
                                  

                                       
                                  


















                                                                                    












































                                                                             
                                                                                         






































                                                                           
                                                                





                                                 
                              








                                                           
                                                   






                                             


                                                             
                                                                     






































                                                                         




                                                 
                                                         



                                                                          



                                                                     


















                                                          
                                                                      




                                                    
                                                   
                                           

 
                                               
                                         
                                        


                                         
                                                




                                                            
                                                          
 


                                                            


                           
                                                     
 






                                                           
                          





                                                             
                                                                     



























                                                                          
                                                                                            



































                                                                       
                                        

                                             
                                                                      











                                                
                                                                                             






                                   
                                                      
 
                                                          
 
                                         




                                                         
                                                           













                                                                       
                                                                                     








                                            
                                                                          
 
                                                         

                    
                




                                                   
                                                     
 
                                                         

                    
                








                                           
                                               



                                         


                                      



                                   
                                                        



                                    
                                                   






                                                           
/*
 *  linux/drivers/mmc/pxa.c - PXA MMCI driver
 *
 *  Copyright (C) 2003 Russell King, All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 *  This hardware is really sick:
 *   - No way to clear interrupts.
 *   - Have to turn off the clock whenever we touch the device.
 *   - Doesn't tell you how many data blocks were transferred.
 *  Yuck!
 *
 *	1 and 3 byte data transfers not supported
 *	max block length up to 1023
 */
#include <linux/module.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/mmc/host.h>
#include <linux/mmc/protocol.h>

#include <asm/dma.h>
#include <asm/io.h>
#include <asm/scatterlist.h>
#include <asm/sizes.h>

#include <asm/arch/pxa-regs.h>
#include <asm/arch/mmc.h>

#include "pxamci.h"

#define DRIVER_NAME	"pxa2xx-mci"

#define NR_SG	1

struct pxamci_host {
	struct mmc_host		*mmc;
	spinlock_t		lock;
	struct resource		*res;
	void __iomem		*base;
	int			irq;
	int			dma;
	unsigned int		clkrt;
	unsigned int		cmdat;
	unsigned int		imask;
	unsigned int		power_mode;
	struct pxamci_platform_data *pdata;

	struct mmc_request	*mrq;
	struct mmc_command	*cmd;
	struct mmc_data		*data;

	dma_addr_t		sg_dma;
	struct pxa_dma_desc	*sg_cpu;
	unsigned int		dma_len;

	unsigned int		dma_dir;
};

static void pxamci_stop_clock(struct pxamci_host *host)
{
	if (readl(host->base + MMC_STAT) & STAT_CLK_EN) {
		unsigned long timeout = 10000;
		unsigned int v;

		writel(STOP_CLOCK, host->base + MMC_STRPCL);

		do {
			v = readl(host->base + MMC_STAT);
			if (!(v & STAT_CLK_EN))
				break;
			udelay(1);
		} while (timeout--);

		if (v & STAT_CLK_EN)
			dev_err(mmc_dev(host->mmc), "unable to stop clock\n");
	}
}

static void pxamci_enable_irq(struct pxamci_host *host, unsigned int mask)
{
	unsigned long flags;

	spin_lock_irqsave(&host->lock, flags);
	host->imask &= ~mask;
	writel(host->imask, host->base + MMC_I_MASK);
	spin_unlock_irqrestore(&host->lock, flags);
}

static void pxamci_disable_irq(struct pxamci_host *host, unsigned int mask)
{
	unsigned long flags;

	spin_lock_irqsave(&host->lock, flags);
	host->imask |= mask;
	writel(host->imask, host->base + MMC_I_MASK);
	spin_unlock_irqrestore(&host->lock, flags);
}

static void pxamci_setup_data(struct pxamci_host *host, struct mmc_data *data)
{
	unsigned int nob = data->blocks;
	unsigned long long clks;
	unsigned int timeout;
	u32 dcmd;
	int i;

	host->data = data;

	if (data->flags & MMC_DATA_STREAM)
		nob = 0xffff;

	writel(nob, host->base + MMC_NOB);
	writel(data->blksz, host->base + MMC_BLKLEN);

	clks = (unsigned long long)data->timeout_ns * CLOCKRATE;
	do_div(clks, 1000000000UL);
	timeout = (unsigned int)clks + (data->timeout_clks << host->clkrt);
	writel((timeout + 255) / 256, host->base + MMC_RDTO);

	if (data->flags & MMC_DATA_READ) {
		host->dma_dir = DMA_FROM_DEVICE;
		dcmd = DCMD_INCTRGADDR | DCMD_FLOWTRG;
		DRCMRTXMMC = 0;
		DRCMRRXMMC = host->dma | DRCMR_MAPVLD;
	} else {
		host->dma_dir = DMA_TO_DEVICE;
		dcmd = DCMD_INCSRCADDR | DCMD_FLOWSRC;
		DRCMRRXMMC = 0;
		DRCMRTXMMC = host->dma | DRCMR_MAPVLD;
	}

	dcmd |= DCMD_BURST32 | DCMD_WIDTH1;

	host->dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
				   host->dma_dir);

	for (i = 0; i < host->dma_len; i++) {
		if (data->flags & MMC_DATA_READ) {
			host->sg_cpu[i].dsadr = host->res->start + MMC_RXFIFO;
			host->sg_cpu[i].dtadr = sg_dma_address(&data->sg[i]);
		} else {
			host->sg_cpu[i].dsadr = sg_dma_address(&data->sg[i]);
			host->sg_cpu[i].dtadr = host->res->start + MMC_TXFIFO;
		}
		host->sg_cpu[i].dcmd = dcmd | sg_dma_len(&data->sg[i]);
		host->sg_cpu[i].ddadr = host->sg_dma + (i + 1) *
					sizeof(struct pxa_dma_desc);
	}
	host->sg_cpu[host->dma_len - 1].ddadr = DDADR_STOP;
	wmb();

	DDADR(host->dma) = host->sg_dma;
	DCSR(host->dma) = DCSR_RUN;
}

static void pxamci_start_cmd(struct pxamci_host *host, struct mmc_command *cmd, unsigned int cmdat)
{
	WARN_ON(host->cmd != NULL);
	host->cmd = cmd;

	if (cmd->flags & MMC_RSP_BUSY)
		cmdat |= CMDAT_BUSY;

#define RSP_TYPE(x)	((x) & ~(MMC_RSP_BUSY|MMC_RSP_OPCODE))
	switch (RSP_TYPE(mmc_resp_type(cmd))) {
	case RSP_TYPE(MMC_RSP_R1): /* r1, r1b, r6, r7 */
		cmdat |= CMDAT_RESP_SHORT;
		break;
	case RSP_TYPE(MMC_RSP_R3):
		cmdat |= CMDAT_RESP_R3;
		break;
	case RSP_TYPE(MMC_RSP_R2):
		cmdat |= CMDAT_RESP_R2;
		break;
	default:
		break;
	}

	writel(cmd->opcode, host->base + MMC_CMD);
	writel(cmd->arg >> 16, host->base + MMC_ARGH);
	writel(cmd->arg & 0xffff, host->base + MMC_ARGL);
	writel(cmdat, host->base + MMC_CMDAT);
	writel(host->clkrt, host->base + MMC_CLKRT);

	writel(START_CLOCK, host->base + MMC_STRPCL);

	pxamci_enable_irq(host, END_CMD_RES);
}

static void pxamci_finish_request(struct pxamci_host *host, struct mmc_request *mrq)
{
	host->mrq = NULL;
	host->cmd = NULL;
	host->data = NULL;
	mmc_request_done(host->mmc, mrq);
}

static int pxamci_cmd_done(struct pxamci_host *host, unsigned int stat)
{
	struct mmc_command *cmd = host->cmd;
	int i;
	u32 v;

	if (!cmd)
		return 0;

	host->cmd = NULL;

	/*
	 * Did I mention this is Sick.  We always need to
	 * discard the upper 8 bits of the first 16-bit word.
	 */
	v = readl(host->base + MMC_RES) & 0xffff;
	for (i = 0; i < 4; i++) {
		u32 w1 = readl(host->base + MMC_RES) & 0xffff;
		u32 w2 = readl(host->base + MMC_RES) & 0xffff;
		cmd->resp[i] = v << 24 | w1 << 8 | w2 >> 8;
		v = w2;
	}

	if (stat & STAT_TIME_OUT_RESPONSE) {
		cmd->error = MMC_ERR_TIMEOUT;
	} else if (stat & STAT_RES_CRC_ERR && cmd->flags & MMC_RSP_CRC) {
#ifdef CONFIG_PXA27x
		/*
		 * workaround for erratum #42:
		 * Intel PXA27x Family Processor Specification Update Rev 001
		 */
		if (cmd->opcode == MMC_ALL_SEND_CID ||
		    cmd->opcode == MMC_SEND_CSD ||
		    cmd->opcode == MMC_SEND_CID) {
			/* a bogus CRC error can appear if the msb of
			   the 15 byte response is a one */
			if ((cmd->resp[0] & 0x80000000) == 0)
				cmd->error = MMC_ERR_BADCRC;
		} else {
			pr_debug("ignoring CRC from command %d - *risky*\n",cmd->opcode);
		}
#else
		cmd->error = MMC_ERR_BADCRC;
#endif
	}

	pxamci_disable_irq(host, END_CMD_RES);
	if (host->data && cmd->error == MMC_ERR_NONE) {
		pxamci_enable_irq(host, DATA_TRAN_DONE);
	} else {
		pxamci_finish_request(host, host->mrq);
	}

	return 1;
}

static int pxamci_data_done(struct pxamci_host *host, unsigned int stat)
{
	struct mmc_data *data = host->data;

	if (!data)
		return 0;

	DCSR(host->dma) = 0;
	dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->dma_len,
		     host->dma_dir);

	if (stat & STAT_READ_TIME_OUT)
		data->error = MMC_ERR_TIMEOUT;
	else if (stat & (STAT_CRC_READ_ERROR|STAT_CRC_WRITE_ERROR))
		data->error = MMC_ERR_BADCRC;

	/*
	 * There appears to be a hardware design bug here.  There seems to
	 * be no way to find out how much data was transferred to the card.
	 * This means that if there was an error on any block, we mark all
	 * data blocks as being in error.
	 */
	if (data->error == MMC_ERR_NONE)
		data->bytes_xfered = data->blocks * data->blksz;
	else
		data->bytes_xfered = 0;

	pxamci_disable_irq(host, DATA_TRAN_DONE);

	host->data = NULL;
	if (host->mrq->stop) {
		pxamci_stop_clock(host);
		pxamci_start_cmd(host, host->mrq->stop, 0);
	} else {
		pxamci_finish_request(host, host->mrq);
	}

	return 1;
}

static irqreturn_t pxamci_irq(int irq, void *devid)
{
	struct pxamci_host *host = devid;
	unsigned int ireg;
	int handled = 0;

	ireg = readl(host->base + MMC_I_REG);

	if (ireg) {
		unsigned stat = readl(host->base + MMC_STAT);

		pr_debug("PXAMCI: irq %08x stat %08x\n", ireg, stat);

		if (ireg & END_CMD_RES)
			handled |= pxamci_cmd_done(host, stat);
		if (ireg & DATA_TRAN_DONE)
			handled |= pxamci_data_done(host, stat);
	}

	return IRQ_RETVAL(handled);
}

static void pxamci_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
	struct pxamci_host *host = mmc_priv(mmc);
	unsigned int cmdat;

	WARN_ON(host->mrq != NULL);

	host->mrq = mrq;

	pxamci_stop_clock(host);

	cmdat = host->cmdat;
	host->cmdat &= ~CMDAT_INIT;

	if (mrq->data) {
		pxamci_setup_data(host, mrq->data);

		cmdat &= ~CMDAT_BUSY;
		cmdat |= CMDAT_DATAEN | CMDAT_DMAEN;
		if (mrq->data->flags & MMC_DATA_WRITE)
			cmdat |= CMDAT_WRITE;

		if (mrq->data->flags & MMC_DATA_STREAM)
			cmdat |= CMDAT_STREAM;
	}

	pxamci_start_cmd(host, mrq->cmd, cmdat);
}

static int pxamci_get_ro(struct mmc_host *mmc)
{
	struct pxamci_host *host = mmc_priv(mmc);

	if (host->pdata && host->pdata->get_ro)
		return host->pdata->get_ro(mmc_dev(mmc));
	/* Host doesn't support read only detection so assume writeable */
	return 0;
}

static void pxamci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
	struct pxamci_host *host = mmc_priv(mmc);

	if (ios->clock) {
		unsigned int clk = CLOCKRATE / ios->clock;
		if (CLOCKRATE / clk > ios->clock)
			clk <<= 1;
		host->clkrt = fls(clk) - 1;
		pxa_set_cken(CKEN12_MMC, 1);

		/*
		 * we write clkrt on the next command
		 */
	} else {
		pxamci_stop_clock(host);
		pxa_set_cken(CKEN12_MMC, 0);
	}

	if (host->power_mode != ios->power_mode) {
		host->power_mode = ios->power_mode;

		if (host->pdata && host->pdata->setpower)
			host->pdata->setpower(mmc_dev(mmc), ios->vdd);

		if (ios->power_mode == MMC_POWER_ON)
			host->cmdat |= CMDAT_INIT;
	}

	pr_debug("PXAMCI: clkrt = %x cmdat = %x\n",
		 host->clkrt, host->cmdat);
}

static const struct mmc_host_ops pxamci_ops = {
	.request	= pxamci_request,
	.get_ro		= pxamci_get_ro,
	.set_ios	= pxamci_set_ios,
};

static void pxamci_dma_irq(int dma, void *devid)
{
	printk(KERN_ERR "DMA%d: IRQ???\n", dma);
	DCSR(dma) = DCSR_STARTINTR|DCSR_ENDINTR|DCSR_BUSERR;
}

static irqreturn_t pxamci_detect_irq(int irq, void *devid)
{
	struct pxamci_host *host = mmc_priv(devid);

	mmc_detect_change(devid, host->pdata->detect_delay);
	return IRQ_HANDLED;
}

static int pxamci_probe(struct platform_device *pdev)
{
	struct mmc_host *mmc;
	struct pxamci_host *host = NULL;
	struct resource *r;
	int ret, irq;

	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	irq = platform_get_irq(pdev, 0);
	if (!r || irq < 0)
		return -ENXIO;

	r = request_mem_region(r->start, SZ_4K, DRIVER_NAME);
	if (!r)
		return -EBUSY;

	mmc = mmc_alloc_host(sizeof(struct pxamci_host), &pdev->dev);
	if (!mmc) {
		ret = -ENOMEM;
		goto out;
	}

	mmc->ops = &pxamci_ops;
	mmc->f_min = CLOCKRATE_MIN;
	mmc->f_max = CLOCKRATE_MAX;

	/*
	 * We can do SG-DMA, but we don't because we never know how much
	 * data we successfully wrote to the card.
	 */
	mmc->max_phys_segs = NR_SG;

	/*
	 * Our hardware DMA can handle a maximum of one page per SG entry.
	 */
	mmc->max_seg_size = PAGE_SIZE;

	host = mmc_priv(mmc);
	host->mmc = mmc;
	host->dma = -1;
	host->pdata = pdev->dev.platform_data;
	mmc->ocr_avail = host->pdata ?
			 host->pdata->ocr_mask :
			 MMC_VDD_32_33|MMC_VDD_33_34;

	host->sg_cpu = dma_alloc_coherent(&pdev->dev, PAGE_SIZE, &host->sg_dma, GFP_KERNEL);
	if (!host->sg_cpu) {
		ret = -ENOMEM;
		goto out;
	}

	spin_lock_init(&host->lock);
	host->res = r;
	host->irq = irq;
	host->imask = MMC_I_MASK_ALL;

	host->base = ioremap(r->start, SZ_4K);
	if (!host->base) {
		ret = -ENOMEM;
		goto out;
	}

	/*
	 * Ensure that the host controller is shut down, and setup
	 * with our defaults.
	 */
	pxamci_stop_clock(host);
	writel(0, host->base + MMC_SPI);
	writel(64, host->base + MMC_RESTO);
	writel(host->imask, host->base + MMC_I_MASK);

	host->dma = pxa_request_dma(DRIVER_NAME, DMA_PRIO_LOW,
				    pxamci_dma_irq, host);
	if (host->dma < 0) {
		ret = -EBUSY;
		goto out;
	}

	ret = request_irq(host->irq, pxamci_irq, 0, DRIVER_NAME, host);
	if (ret)
		goto out;

	platform_set_drvdata(pdev, mmc);

	if (host->pdata && host->pdata->init)
		host->pdata->init(&pdev->dev, pxamci_detect_irq, mmc);

	mmc_add_host(mmc);

	return 0;

 out:
	if (host) {
		if (host->dma >= 0)
			pxa_free_dma(host->dma);
		if (host->base)
			iounmap(host->base);
		if (host->sg_cpu)
			dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);
	}
	if (mmc)
		mmc_free_host(mmc);
	release_resource(r);
	return ret;
}

static int pxamci_remove(struct platform_device *pdev)
{
	struct mmc_host *mmc = platform_get_drvdata(pdev);

	platform_set_drvdata(pdev, NULL);

	if (mmc) {
		struct pxamci_host *host = mmc_priv(mmc);

		if (host->pdata && host->pdata->exit)
			host->pdata->exit(&pdev->dev, mmc);

		mmc_remove_host(mmc);

		pxamci_stop_clock(host);
		writel(TXFIFO_WR_REQ|RXFIFO_RD_REQ|CLK_IS_OFF|STOP_CMD|
		       END_CMD_RES|PRG_DONE|DATA_TRAN_DONE,
		       host->base + MMC_I_MASK);

		DRCMRRXMMC = 0;
		DRCMRTXMMC = 0;

		free_irq(host->irq, host);
		pxa_free_dma(host->dma);
		iounmap(host->base);
		dma_free_coherent(&pdev->dev, PAGE_SIZE, host->sg_cpu, host->sg_dma);

		release_resource(host->res);

		mmc_free_host(mmc);
	}
	return 0;
}

#ifdef CONFIG_PM
static int pxamci_suspend(struct platform_device *dev, pm_message_t state)
{
	struct mmc_host *mmc = platform_get_drvdata(dev);
	int ret = 0;

	if (mmc)
		ret = mmc_suspend_host(mmc, state);

	return ret;
}

static int pxamci_resume(struct platform_device *dev)
{
	struct mmc_host *mmc = platform_get_drvdata(dev);
	int ret = 0;

	if (mmc)
		ret = mmc_resume_host(mmc);

	return ret;
}
#else
#define pxamci_suspend	NULL
#define pxamci_resume	NULL
#endif

static struct platform_driver pxamci_driver = {
	.probe		= pxamci_probe,
	.remove		= pxamci_remove,
	.suspend	= pxamci_suspend,
	.resume		= pxamci_resume,
	.driver		= {
		.name	= DRIVER_NAME,
	},
};

static int __init pxamci_init(void)
{
	return platform_driver_register(&pxamci_driver);
}

static void __exit pxamci_exit(void)
{
	platform_driver_unregister(&pxamci_driver);
}

module_init(pxamci_init);
module_exit(pxamci_exit);

MODULE_DESCRIPTION("PXA Multimedia Card Interface Driver");
MODULE_LICENSE("GPL");