/*======================================================================

    High-level PCMCIA driver interface

    Written by David Hinds, dhinds@allegro.stanford.edu
    
======================================================================*/

#include <linux/types.h>
#include <linux/termios.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/tty.h>
#include <linux/fcntl.h>
#include <linux/string.h>
#include <linux/pcmcia.h>

#include <asm/io.h>
#include <asm/bitops.h>
#include <asm/segment.h>
#include <asm/system.h>

#include "i82365.h"

/*====================================================================*/

#define MAX_SLOTS 8

static unsigned int slots_present = 0;

static struct pcmcia_slot slot_table[MAX_SLOTS] = {
  { 0, 0, 0 },
  { 0, 0, 0 },
  { 0, 0, 0 },
  { 0, 0, 0 },
  { 0, 0, 0 },
  { 0, 0, 0 },
  { 0, 0, 0 },
  { 0, 0, 0 }
  };

static struct pcmcia_ctrl ctrl;

/*====================================================================*/

static int pcmcia_read(struct inode *inode, struct file *file,
                        char *buf, int count);
static int pcmcia_select(struct inode *inode, struct file *file,
                         int sel_type, select_table *wait);
static int pcmcia_ioctl(struct inode * inode, struct file * file,
                        unsigned int cmd, unsigned long arg);
static int pcmcia_open(struct inode * inode, struct file * file);
static void pcmcia_release(struct inode * inode, struct file * file);
static void pcmcia_interrupt(int irq);

static struct file_operations pcmcia_fops = {
  NULL,           /* lseek */
  pcmcia_read,    /* read */
  NULL,           /* write */
  NULL,           /* readdir */
  pcmcia_select,  /* select */
  pcmcia_ioctl,   /* ioctl */
  NULL,           /* mmap */
  pcmcia_open,    /* open */
  pcmcia_release, /* release */
  NULL            /* fsync */
  };

/*====================================================================*/

unsigned long pcmcia_init(unsigned long kmem_start,
                          unsigned long kmem_end)
  {
  unsigned int ret;
  
#ifdef CONFIG_I82365
  ret = i365_probe(&ctrl);
  if (ret > 0) slots_present = ret;
#endif
  
#ifdef CONFIG_TCIC
  ret = tcic_probe(&ctrl);
  if (ret > 0) slots_present = ret;
#endif

  /* Grab an interrupt */
  if (slots_present > 0) {
    int irqlist[] = { 15, 14, 12, 11, 10, 9, 7, 5, 4, 3, 0 };
    int *irqp;
    for (irqp = irqlist; *irqp != 0; irqp++)
      if (request_irq(*irqp, &pcmcia_interrupt) == 0) break;
    ctrl.csc_irq = *irqp;
    if (*irqp == 0)
      printk("unable to get IRQ for PCMCIA card status changes\n");
    else
      printk("Card status change interrupts on IRQ %d\n", *irqp);
    }
  
  if (register_chrdev(PCMCIA_MAJOR, "pc", &pcmcia_fops)) {
    printk("unable to get major %d for PCMCIA controller\n",
           PCMCIA_MAJOR);
    }
  return kmem_start;
  } /* pcmcia_init */

/*====================================================================*/

static void pcmcia_interrupt(int irq)
  {
  int i, ret;
/*  printk("pcmcia_interrupt()\n");*/
  for (i = 0; i < slots_present; i++) {
    ret = ctrl.csc_poll(i);
    if (ret == 0) continue;
/*    printk("  csc_poll(slot %d) = %d\n", i, ret);*/
    if (slot_table[i].flags & PCMCIA_BUSY) {
      slot_table[i].flags |= PCMCIA_EVENT;
      slot_table[i].event |= ret;
      wake_up_interruptible(&slot_table[i].queue);
      }
    }
  } /* pcmcia_interrupt */
  
/*====================================================================*/

static int pcmcia_read(struct inode *inode, struct file *file,
                       char *buf, int count)
  {
  unsigned int slot = MINOR(inode->i_rdev);
/*  printk("pcmcia_read(slot %d)\n", slot);*/
  if (slot >= slots_present)
    return -ENODEV;
  if (count < 4)
    return -EINVAL;
  if ((slot_table[slot].flags & PCMCIA_EVENT) == 0) {
    interruptible_sleep_on(&slot_table[slot].queue);
    if (current->signal & ~current->blocked) return -EINTR;
    }
  if ((slot_table[slot].flags & PCMCIA_EVENT) == 0) {
    printk("pcmcia_read(): woke up for nothing!\n");
    return -EAGAIN;
    }
  put_fs_long(slot_table[slot].event, buf);
  slot_table[slot].flags &= ~PCMCIA_EVENT;
  ctrl.csc_ack(slot, slot_table[slot].event);
  slot_table[slot].event = 0;
  return 4;
  } /* pcmcia_read */

static int pcmcia_select(struct inode *inode, struct file *file,
                         int sel_type, select_table *wait)
  {
  unsigned int slot = MINOR(inode->i_rdev);
/*  printk("pcmcia_select(slot %d)\n", slot);*/
  if (sel_type != SEL_IN)
    return 0;
  if (slot_table[slot].flags & PCMCIA_EVENT)
    return 1;
  select_wait(&slot_table[slot].queue, wait);
  return 0;
  } /* pcmcia_select */
  
static int pcmcia_ioctl(struct inode * inode, struct file * file,
                        unsigned int cmd, unsigned long arg)
  {
  unsigned int slot = MINOR(inode->i_rdev);
  if (slot >= slots_present) return -ENODEV;
  return ctrl.ioctl(slot, cmd, arg);
  } /* pcmcia_ioctl */

static int pcmcia_open(struct inode *inode, struct file *file)
  {
  unsigned int slot = MINOR(inode->i_rdev);
/*  printk("pcmcia_open(slot %d)\n", slot);*/
  if (slot >= slots_present) return -ENODEV;
  if (slot_table[slot].flags & PCMCIA_BUSY) return -EBUSY;
  slot_table[slot].flags |= PCMCIA_BUSY;
  ctrl.csc_on(slot, ctrl.csc_irq);
  return 0;
  } /* pcmcia_open */

static void pcmcia_release(struct inode *inode, struct file *file)
  {
  unsigned int slot = MINOR(inode->i_rdev);
/*  printk("pcmcia_release(slot %d)\n", slot);*/
  slot_table[slot].flags &= ~PCMCIA_BUSY;
  if (slot_table[slot].flags & PCMCIA_EVENT) {
    slot_table[slot].flags &= ~PCMCIA_EVENT;
    ctrl.csc_ack(slot, slot_table[slot].event);
    slot_table[slot].event = 0;
    }
  ctrl.csc_off(slot);
  } /* pcmcia_release */
