#include "hardware.h"
#ifdef KERNEL20
#include <asm/segment.h>
#endif
#ifdef KERNEL21
#include <asm/uaccess.h>
#endif
#include <asm/io.h>
#include <asm/delay.h>
#include <linux/module.h>
#include <linux/fs.h>


/* Global pointers to the LED adresses */
volatile unsigned char *LED1;
volatile unsigned char *LED2;

/* LED module usage counter */
static int led_usage=0;

/* The last value written to the LEDs (for readback)
   We initialize with 0 because init() will write 0 to the LEDs */
static unsigned char last_led=0;


/*
 * The module's open function
 */
int led_open(struct inode *inode, struct file *file)
{
  if (led_usage!=0)
    return -EBUSY;

  MOD_INC_USE_COUNT;
  led_usage=1;

#ifdef DEBUG
  printk(KERN_INFO "LED opened\n");
#endif

  return(0);
}

/*
 * The module's release function (when closed)
 */
#ifdef KERNEL20
void led_release(struct inode *inode, struct file *mfile)
#endif
#ifdef KERNEL21
int led_release(struct inode *inode, struct file *mfile)
#endif
{
  MOD_DEC_USE_COUNT;
  led_usage=0;

#ifdef DEBUG
  printk(KERN_INFO "LED module closed\n");
#endif

#ifdef KERNEL21
  return 0;
#endif
}

/*
 * The most important part: writing to the LEDs ;)
 */
#ifdef KERNEL20
int led_write_byte(struct inode *inode, struct file *mfile, const char *gdata, int length)
#endif
#ifdef KERNEL21
ssize_t led_write_byte(struct file *inode, const char *gdata, size_t length, loff_t *off_what)
#endif
{
unsigned int i;
const char *data;
unsigned char c;

  data=gdata;
  for (i=0;i<length;i++) {
#ifdef KERNEL20
    c=get_user((char *)(data+i));
#endif
#ifdef KERNEL21
    get_user(c,(char *)(data+i));
#endif
#ifdef DEBUG
  printk(KERN_INFO "LED: length=%d\n",length);
#endif
      /* For what reason ever we have to write the value to one adress
         and ~value to the other address (some kind of latch?) */
      *LED1=c;
      *LED2=~c;
      last_led=c;
#ifdef DEBUG
  printk(KERN_INFO "LED: data[%d] written\n",length-1);
#endif
  }

  return(length);
}

/*
 * The LEDs cannot be read directly so we simulate this with a static variable
 * WARNING: read() never reaches an end so EOF never occurs!
 */
#ifdef KERNEL20
int led_read_byte(struct inode *inode, struct file *mfile, char *data, int length)
#endif
#ifdef KERNEL21
ssize_t led_read_byte(struct file *inode, char *udata, size_t length, loff_t *loff_what)
#endif
{
  put_user(last_led,udata);
  return(1);
}

/*
 * Called when the module is to be unaloaded.
 * For the LEDs we do not have to do much to clean-up, just the normal
 * module cleanup
 */
void cleanup_module(void)
{
  if (unregister_chrdev(LED_MAJOR,"diag-leds"))
    printk(KERN_INFO "LED: module cleanup failed\n");
  else
    printk(KERN_INFO "LED: module cleanup succeeded\n");
}

/*
 * This will initialize our module properly
 */
int init_module(void)
{
  static struct file_operations led_fops={NULL,led_read_byte,led_write_byte,
            NULL,NULL,NULL,NULL,led_open,led_release,
            NULL,NULL,NULL,NULL,NULL};
  if (register_chrdev(LED_MAJOR,"diag-leds",&led_fops))
    printk(KERN_INFO "unable to get major %d for diag-leds device\n",LED_MAJOR);
  else {
    printk(KERN_INFO "Diag-LEDs device V0.21 init:\n");
    /* Here we calculate the virtual LED addresses from the physical
       addresses */
    LED1=(unsigned char *)phys_to_virt((unsigned long)LED_ADDRESS1);
    LED2=(unsigned char *)phys_to_virt((unsigned long)LED_ADDRESS2);
    printk(KERN_INFO "  LED1 address at virtual %#lx\n",(unsigned long)LED1);
    printk(KERN_INFO "  LED2 address at virtual %#lx\n",(unsigned long)LED2);
    led_usage=0;
    *LED1=0;
    *LED2=0xff;
  }
  return 0;
}
