#include <linux/module.h>

#if defined(CONFIG_SMP)
#define __SMP__
#endif

#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/kernel.h>

#include "kb_bells.h"
#define KB_BELLS_MAJOR 42

MODULE_LICENSE ("GPL");

static int kb_bells_ioctl (struct inode *inode, struct file *file,
                           unsigned int cmd, unsigned long arg);
static int kb_bells_open (struct inode *inode, struct file *file);
static int kb_bells_release (struct inode *inode, struct file *file);
static struct file_operations kb_bells_fops = {
  .owner =   THIS_MODULE,
  .ioctl =   kb_bells_ioctl,
  .open =    kb_bells_open,
  .release = kb_bells_release, 
};
static void play_bell_sound (void);
static void play_dupe_sound (void);
static void play_message_sound (void);
static void play_band_change_sound (void);
static void play_single_mult_sound (void);
static void play_double_mult_sound (void);
static void play_bad_call_sound (void);
static void play_bad_key_sound (void);
static void play_speed_change_sound (void);

static __init int kb_init_module (void)
{
  int res;

  res = register_chrdev (KB_BELLS_MAJOR, "kb_bells", &kb_bells_fops);
  printk (KERN_DEBUG "kb_bells inserted\n");
  return res;
}

module_init (kb_init_module);

static __exit void kb_cleanup_module (void)
{
  unregister_chrdev(KB_BELLS_MAJOR, "kb_bells");
  printk (KERN_DEBUG "kb_bells removed\n");
}

module_exit (kb_cleanup_module);

static int kb_bells_open (struct inode *inode, struct file *file)
{
  asm ("push %ax;mov $0xb6,%al;out %al,$0x43;pop %ax;");
  printk (KERN_DEBUG "kb_bells opened\n");
  return 0; 
}

static int kb_bells_release (struct inode *inode, struct file *file)
{
  printk (KERN_DEBUG "kb_bells closed\n");
  return 0;
}

static int kb_bells_ioctl (struct inode *inode, struct file *file,
                           unsigned int cmd, unsigned long arg)
{
  if (_IOC_TYPE (cmd) != KB_BELLS_IOCTL_BASE) return -ENOTTY;
  switch (_IOC_NR (cmd))
   {
    case BELL_SOUND :
     play_bell_sound ();
     break;
    case DUPE_SOUND :
     play_dupe_sound ();
     break;
    case MESSAGE_SOUND :
     play_message_sound ();
     break;
    case BAND_CHANGE_SOUND :
     play_band_change_sound ();
     break;
    case SINGLE_MULT_SOUND :
     play_single_mult_sound ();
     break;
    case DOUBLE_MULT_SOUND :
     play_double_mult_sound ();
     break;
    case BAD_CALL_SOUND :
     play_bad_call_sound ();
     break;
    case BAD_KEY_SOUND :
     play_bad_key_sound ();
     break;
    case SPEED_CHANGE_SOUND :
     play_speed_change_sound ();
     break;
   }
  asm ("push %ax;mov $0x00,%al;out %al,$0x61;pop %ax;");
  return 0;
}

static void play_bell_sound (void)
{
  int tone_msec = 60;

asm ("push %ax;mov $0x74,%al;out %al,$0x42;mov $0x04,%al;out %al,$0x42;in $0x61,%al;or $0x03,%al;out %al,$0x61;pop %ax;");
  mdelay (tone_msec);
}

static void play_dupe_sound (void)
{
  int tone_msec = 120;

asm ("push %ax;mov $0xe0,%al;out %al,$0x42;mov $0x0f,%al;out %al,$0x42;in $0x61,%al;or $0x03,%al;out %al,$0x61;pop %ax;");
  mdelay (tone_msec);
asm ("push %ax;mov $0xc8,%al;out %al,$0x42;mov $0x17,%al;out %al,$0x42;in $0x61,%al;or $0x03,%al;out %al,$0x61;pop %ax;");
  mdelay (tone_msec);
}

static void play_message_sound (void)
{
  int tone_msec = 120;

asm ("push %ax;mov $0x58,%al;out %al,$0x42;mov $0x0d,%al;out %al,$0x42;in $0x61,%al;or $0x03,%al;out %al,$0x61;pop %ax;");
  mdelay (tone_msec);
asm ("push %ax;mov $0xe8,%al;out %al,$0x42;mov $0x08,%al;out %al,$0x42;in $0x61,%al;or $0x03,%al;out %al,$0x61;pop %ax;");
  mdelay (tone_msec);
asm ("push %ax;mov $0x58,%al;out %al,$0x42;mov $0x0d,%al;out %al,$0x42;in $0x61,%al;or $0x03,%al;out %al,$0x61;pop %ax;");
  mdelay (tone_msec);
}

static void play_band_change_sound (void)
{
  int tone_msec = 60;

asm ("push %ax;mov $0x10,%al;out %al,$0x42;mov $0x07,%al;out %al,$0x42;in $0x61,%al;or $0x03,%al;out %al,$0x61;pop %ax;");
  mdelay (tone_msec);
asm ("push %ax;mov $0x00,%al;out %al,$0x42;mov $0x00,%al;out %al,$0x42;in $0x61,%al;or $0x03,%al;out %al,$0x61;pop %ax;");
  mdelay (tone_msec);
asm ("push %ax;mov $0x10,%al;out %al,$0x42;mov $0x07,%al;out %al,$0x42;in $0x61,%al;or $0x03,%al;out %al,$0x61;pop %ax;");
  mdelay (tone_msec);
asm ("push %ax;mov $0x00,%al;out %al,$0x42;mov $0x00,%al;out %al,$0x42;in $0x61,%al;or $0x03,%al;out %al,$0x61;pop %ax;");
  mdelay (tone_msec);
asm ("push %ax;mov $0x10,%al;out %al,$0x42;mov $0x07,%al;out %al,$0x42;in $0x61,%al;or $0x03,%al;out %al,$0x61;pop %ax;");
  mdelay (tone_msec);
}

static void play_single_mult_sound (void)
{
  int tone_msec = 60;

asm ("push %ax;mov $0xe8,%al;out %al,$0x42;mov $0x08,%al;out %al,$0x42;in $0x61,%al;or $0x03,%al;out %al,$0x61;pop %ax;");
  mdelay (tone_msec);
asm ("push %ax;mov $0xac,%al;out %al,$0x42;mov $0x06,%al;out %al,$0x42;in $0x61,%al;or $0x03,%al;out %al,$0x61;pop %ax;");
  mdelay (tone_msec);
}

static void play_double_mult_sound (void)
{
  int tone_msec = 60;

asm ("push %ax;mov $0xe8,%al;out %al,$0x42;mov $0x08,%al;out %al,$0x42;in $0x61,%al;or $0x03,%al;out %al,$0x61;pop %ax;");
  mdelay (tone_msec);
asm ("push %ax;mov $0xac,%al;out %al,$0x42;mov $0x06,%al;out %al,$0x42;in $0x61,%al;or $0x03,%al;out %al,$0x61;pop %ax;");
  mdelay (tone_msec);
asm ("push %ax;mov $0x00,%al;out %al,$0x42;mov $0x00,%al;out %al,$0x42;in $0x61,%al;or $0x03,%al;out %al,$0x61;pop %ax;");
  mdelay (tone_msec);
asm ("push %ax;mov $0xe8,%al;out %al,$0x42;mov $0x08,%al;out %al,$0x42;in $0x61,%al;or $0x03,%al;out %al,$0x61;pop %ax;");
  mdelay (tone_msec);
asm ("push %ax;mov $0xac,%al;out %al,$0x42;mov $0x06,%al;out %al,$0x42;in $0x61,%al;or $0x03,%al;out %al,$0x61;pop %ax;");
  mdelay (tone_msec);
}

static void play_bad_call_sound (void)
{
  int tone_msec = 380;

asm ("push %ax;mov $0xd0,%al;out %al,$0x42;mov $0x11,%al;out %al,$0x42;in $0x61,%al;or $0x03,%al;out %al,$0x61;pop %ax;");
  mdelay (tone_msec);
}

static void play_bad_key_sound (void)
{
  int tone_msec = 420;

asm ("push %ax;mov $0xcc,%al;out %al,$0x42;mov $0x17,%al;out %al,$0x42;in $0x61,%al;or $0x03,%al;out %al,$0x61;pop %ax;");
  mdelay (tone_msec);
}

static void play_speed_change_sound (void)
{
  int tone_msec = 60;

asm ("push %ax;mov $0x10,%al;out %al,$0x42;mov $0x07,%al;out %al,$0x42;in $0x61,%al;or $0x03,%al;out %al,$0x61;pop %ax;");
  mdelay (tone_msec);
}
