/*
 *  linux/arch/arm/drivers/block/ide-simtec.c
 *
 * Copyright (c) 2001 Ian Molton (spyro@f2s.com)
 *
 * Changelog:
 *  27-05-2002  IM	* Cleanup prior to sending to Ben
 * 
 *  30-07-2001	IM	* Complete re-write for 2.4, inspired by
 *			  Ben Dooks original code.
 *			* Fast IO code using mmio-acorn.S
 * 
 * DO NOT EMAIL BEN DOOKS OR GARETH SIMPSON FOR SUPPORT OF THIS DRIVER
 *
 * *DO* email me (spyro@armlinux.org) and I will endevour to help. 
 *
 * Thanks to gareth@simtec for providing the hardware details.
 *
 * This code is still considered BETA, and should be treated with care.
 * it has been tested with linux 2.4.17 compiled on an arm based machine
 *
 */

/* current known problems:
 *
 * - no 8bit mode IO
 * - doesnt do fast IO yet (although support is there for reads)
 * - dosen't declaim ec-card if it fails to initialise it
 *
 */


#include <linux/module.h>
#include <linux/slab.h>
#include <linux/blkdev.h>
#include <linux/errno.h>
#include <linux/ioport.h>
#include <linux/delay.h>
#include <linux/module.h>

#include <asm/ecard.h>
#include <asm/io.h>

#include <linux/ide.h>

#define DEBUG
#define DRV_PREFIX      "ide-simtec"
#define DRV_VERSION     "(1.00) [10-Dec-01]"

#define CARD_CTRL	(0x2000 >> 2)  /* Card control registers */
#define CARD_IRQ	(0x2004 >> 2)  /* Card IRQ register */
#define CARD_STAT	CARD_IRQ        /* Card status register */

#define IDE_DATA_PRI    (0x0000 >> 2) /* IDE port offsets */
#define IDE_DATA_SEC	(0x1000 >> 2)
#define REG_SPACING	(0x0080 >> 2) /* Spacing of IDE registers */
#define IDE_CTRL_REG_NO	14            /* Control register is card reg. 14 */

/* Card control register values */
#define CTRL_RESET      (0x80)
#define CTRL_IORDY      (0x40)
#define CTRL_MODE       (0x20)
#define CTRL_ENABLE     (0x10)
#define CTRL_SLOW       (0x08)
#define CTRL_ROMW       (0x04)
#define CTRL_SEC_IRQ    (0x02)
#define CTRL_PRI_IRQ    (0x01)

/* Card status registers */
#define STAT_RESET      (0x80)
#define STAT_IORDY      (0x40)
#define STAT_ADDR       (0x20)
#define STAT_CS         (0x10)
#define STAT_RW         (0x08)
#define STAT_IRQ        (0x01)
#define STAT_FAULT      (STAT_ADDR | STAT_CS | STAT_RW | STAT_RESET) /* 0xf8 */

#define MODE_8BIT       (0)
#define MODE_16BIT      (1)

typedef struct simtec_info {
        unsigned int mode;
        unsigned long base;
        unsigned int primary;
        unsigned int secondary;
} simtec_info_t;

static struct expansion_card *ec[MAX_ECARDS];
static struct simtec_info si[MAX_ECARDS];

static void simtec_ide_irqen(struct expansion_card *ec, int irqno)
{
	unsigned int ctrl;
	struct simtec_info *si = (struct simtec_info *)ec->irq_data;
#ifdef DEBUG
	printk(DRV_PREFIX ": enable irq %d\n", irqno);
#endif
	ctrl = inb(si->base + CARD_CTRL);
	if(si->primary   != -1) ctrl |= CTRL_PRI_IRQ;
	if(si->secondary != -1) ctrl |= CTRL_SEC_IRQ;
	outb(ctrl, si->base + CARD_CTRL);
}

static void simtec_ide_irqdis(struct expansion_card *ec, int irqno)
{
	struct simtec_info *si = (struct simtec_info *)ec->irq_data;
#ifdef DEBUG
	printk(DRV_PREFIX ": disable irq %d\n", irqno);
#endif
	outb(inb(si->base + CARD_CTRL) & ~(CTRL_PRI_IRQ | CTRL_SEC_IRQ),
	     si->base + CARD_CTRL);
}

static int simtec_ide_irqpend(struct expansion_card *ec)
{
	struct simtec_info *si = (struct simtec_info *)ec->irq_data;
	return inb(si->base + CARD_IRQ) & STAT_IRQ;
}

#ifdef MODULE
static void simtec_ide_deregister(struct expansion_card *ec, simtec_info_t *si)
{
	/* disable IRQ before deregistering interface */
	outb(inb(si->base + CARD_CTRL) & ~(CTRL_PRI_IRQ | CTRL_SEC_IRQ),
	     si->base + CARD_CTRL);

	if (si->primary >= 0) {
		ide_deregister (si->primary);
		si->primary = -1;
	}
  
	if (si->secondary >= 0) {
		ide_deregister (si->secondary);
		si->secondary = -1;
	}
}
#endif

/* experimental fast transfer code. ------------------------------- */

#if 0
static void simtec_ide_input_data(ide_drive_t *drive, void *buffer,
                                  unsigned int wcount)
{
#ifdef DEBUG
	if(drive->io_32bit)
		printk(DRV_PREFIX "BUG: attempt to use 32 bit IO on 16 bit interface\n");
#endif
	mmio_read_8r(IDE_DATA_REG, buffer, wcount<<1);
}

#endif

/* ---------------------------------------------------------------- */

static void simtec_ide_reset(struct simtec_info *si)
{
	unsigned int ctrl;
#ifdef DEBUG
	printk(DRV_PREFIX ": resetting card... ");
#endif
	outb((ctrl = inb(si->base + CARD_CTRL)) |
	     CTRL_RESET, si->base + CARD_CTRL);
	udelay(500000);		/* 4ms for reset to take effect */
	outb(ctrl, si->base + CARD_CTRL);
#ifdef DEBUG
	if(inb(si->base + CARD_STAT) & STAT_RESET)
		printk(" OK\n");
	else
		printk(" FAILED");
#endif
}

static int simtec_ide_register_port(unsigned long port, int ecard_irq){
	int retcode, i;
	hw_regs_t hw;

        for (i = IDE_DATA_OFFSET; i <= IDE_STATUS_OFFSET; i++) {
                hw.io_ports[i] = (ide_ioreg_t)port + (REG_SPACING * (i-IDE_DATA_OFFSET));
        }
	hw.io_ports[IDE_IRQ_OFFSET] = 0;
	hw.io_ports[IDE_CONTROL_OFFSET] = (ide_ioreg_t)port +
	                                  (REG_SPACING * IDE_CTRL_REG_NO);

	hw.irq = ecard_irq;

#if 0   /* Experimental fast IO code. */
	hw.ide_input_data  = simtec_ide_input_data;
	hw.ide_output_data = NULL;
#endif

        if ((retcode = ide_register_hw(&hw, NULL)) < 0){
                printk(DRV_PREFIX ": error %d registering port (%lx)\n",
               	       retcode, port);
        }
#ifdef DEBUG
        else {
                printk(DRV_PREFIX ": registered port (%lx)\n",
                port);
        }
#endif
	return retcode;
}

static const expansioncard_ops_t simtec_ide_ops = {
	simtec_ide_irqen,
	simtec_ide_irqdis,
	simtec_ide_irqpend,
	NULL,
	NULL,
	NULL
};

static void simtec_ide_register (struct expansion_card *ec, simtec_info_t *si)
{
	unsigned int stat;

	si->base = ecard_address (ec, ECARD_MEMC, 0);

	ec->ops = (expansioncard_ops_t *)&simtec_ide_ops;

	ec->irq_data = (void *)si;
	ec->irqaddr = (unsigned char *)ioaddr(si->base + CARD_IRQ)
	ec->irqmask = STAT_IRQ;

	si->primary = -1;
	si->secondary = -1;
  
	/* set control register to defaults */
	/* We disable IRQs here. linux will enable them by itself. */
	outb(CTRL_ENABLE | CTRL_IORDY, si->base + CARD_CTRL);

	simtec_ide_reset(si);           /* then try resetting the card... */

#ifdef DEBUG
	printk(DRV_PREFIX ": probing card at 0x%lx, irq %i\n",
	       si->base, ec->irq);
	printk(DRV_PREFIX ": ctrl=0x%x, stat=0x%x\n",
	       inb(si->base + CARD_CTRL), inb(si->base + CARD_STAT));
#endif

	/* Check to see if we are an 8 bit podule or not */

	if ((inb(si->base + CARD_CTRL) & CTRL_MODE) != 0) {
		printk(DRV_PREFIX ": found 8bit podule at 0x%lx\n", si->base);
		printk(DRV_PREFIX ": driver does not support 8bit podules\n");
		si->mode = MODE_8BIT;
		return;		/* give up, not capable atm! */
	}
	else {
		si->mode = MODE_16BIT;
		printk(DRV_PREFIX ": found 16bit podule at 0x%lx\n", si->base);
	}

	/* Is there a problem? */
       
	if (((stat = inb(si->base + CARD_STAT)) & STAT_FAULT) != 0) {
		printk(DRV_PREFIX ": warning: cable/card fault? (%0x)\n", stat);
	}
  
	/* Set up the ports on the interface */
	si->primary   = simtec_ide_register_port(si->base + IDE_DATA_PRI,
	                                         ec->irq);
	si->secondary = simtec_ide_register_port(si->base + IDE_DATA_SEC,
	                                         ec->irq);

#ifdef DEBUG
	printk(DRV_PREFIX ": initialised podule.\n");
#endif
}

static const card_ids __init simtec_cids[] = {
	{ 0x005f, 0x130 },
	{ 0x005f, 0x131 } 
};

int __init simtec_ide_init (void)
{
  int i;
  
  printk("SimTec IDE driver, beta release (c) 2001, I Molton\n");
  printk(DRV_PREFIX ": version " DRV_VERSION "\n");
  
  for (i = 0; i < MAX_ECARDS; i++)
    ec[i] = NULL;
  
  ecard_startfind ();

  for (i = 0; ; i++) {
    if ((ec[i] = ecard_find (2, simtec_cids)) == NULL)
      break;
    
    ecard_claim (ec[i]);
    simtec_ide_register (ec[i], &si[i]);
  }
  return 0;
}

#ifdef MODULE

static int init_module (void)
{
  return simtec_ide_init();
}

static void cleanup_module (void)
{
  int i;
  
  for (i = 0; i < MAX_ECARDS; i++)
    if (ec[i]) {
      unsigned long port;
      port = ecard_address (ec[i], ECARD_MEMC, 0);
      
      simtec_ide_deregister (ec[i], &si[i]);
      ecard_release (ec[i]);
      ec[i] = NULL;
    }
}

module_init(init_module);
module_exit(cleanup_module);

#endif


MODULE_LICENSE("GPL");
MODULE_AUTHOR("(c) Ian Molton (spyro@armlinux.org)");
MODULE_DESCRIPTION("Driver for SimTec 16 bit IDE podules");














