/***************************************
* CWKEYER 0.1                          *
*                                      *
* Author : Ivo Simicevic, 9A3TY        *
*          ivo@ultra.hr                *
*                                      *
* This code is under GPL               *
* Modified by PA0RCT for TLF-0.2       *
****************************************/

   /*
     Communication:	CWTONE = 0 : Mute
     			CWTONE = 1 : Stop message
     			CWTONE < 200 : Weight in ms.
     			CWTONE >= 200 : Sidetone in Hz
   */

#include <linux/stddef.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <asm/io.h>
#include <linux/timer.h>
#include <linux/fs.h>
#include <linux/ioport.h>
#include <asm/uaccess.h>
#include <linux/delay.h>

#include "cwkeyioctl.h"


#define RBR(iobase) (iobase+0)
#define THR(iobase) (iobase+0)
#define IER(iobase) (iobase+1)
#define IIR(iobase) (iobase+2)
#define FCR(iobase) (iobase+2)
#define LCR(iobase) (iobase+3)
#define MCR(iobase) (iobase+4)
#define LSR(iobase) (iobase+5)
#define MSR(iobase) (iobase+6)
#define SCR(iobase) (iobase+7)
#define DLL(iobase) (iobase+0)
#define DLM(iobase) (iobase+1)

#define SER_EXTENT 8

#define BUF_SIZE 500

MODULE_PARM(iobase,"i");
MODULE_PARM(irq,"i");

static int major=230;
static int dot_ticks=5;			// Default keyer speed is 60 lpm
static int dash_ticks=15;
static unsigned int count=1491;		// Counter value for 800 Hz
static unsigned int remainder=0;
static unsigned int msweight=0;
static unsigned int stopflag=0;

static int iobase = 0x3f8;
static int irq = 4;

static int inDotDash;
static int mute=0;

void key_out_off ()
{
  outb (0x02,MCR(iobase));
  if (!mute) outb(inb_p(0x61)&0xFC, 0x61);
  return;
}

void key_out_on (unsigned int ticks)
{

/* changes in structure in timer.h for kernel 2.4.x - VE3RZ */

static struct timer_list sound_timer = { { NULL,NULL}, 0, 0, key_out_off, NULL};


  outb (0x03,MCR(iobase));			 /* DTR line */
  del_timer(&sound_timer);
  if (!mute) {
    outb_p(inb_p(0x61)|3, 0x61);
    outb_p(0xB6, 0x43);
    outb_p(count & 0xff, 0x42);
    outb((count >> 8) & 0xff, 0x42);
  }
  if (ticks == dot_ticks)
  	udelay (remainder + (1000 * msweight));
  
  	
  else
  	udelay  (3 * remainder + (1000 * msweight));

	if (ticks) {
    sound_timer.expires = jiffies+ticks;
    add_timer(&sound_timer);
  }
  return;
}

int cwkeyer_open (struct inode *cwinode,struct file *cwfile)
{
  MOD_INC_USE_COUNT;
  return 0;
}

int cwkeyer_release (struct inode *cwinode,struct file *cwfile)
{
  key_out_off ();
  MOD_DEC_USE_COUNT;
  return 0;
}

void dot_pause ()
{
  current->state = TASK_INTERRUPTIBLE;
  schedule_timeout (dot_ticks+dot_ticks);
  udelay(remainder);
  return;
}

void dash_pause ()
{
  current->state = TASK_INTERRUPTIBLE;
  schedule_timeout (dash_ticks+dot_ticks);
  udelay (remainder);
  return;
}

short morse_code [128] = {                                                                          
  0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,   // ..... .....                      
  0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,   // ..... .....                      
  0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,   // ..... .....                      
  0x00,0x00,0x00,0x10,0x48, 0x00,0x13,0x00,0x40,0x78,   // .. !" #$%&'
  0xB0,0xB4,0x00,0x50,0xCC, 0x84,0x54,0x90,0xF8,0x78,   // ()*+, -./01                      
  0x38,0x18,0x08,0x00,0x80, 0xC0,0xE0,0xF0,0xE0,0xA8,   // 23456 789:;                      
  0x14,0x88,0x8A,0x30,0x00, 0x40,0x80,0xA0,0x80,0x00,   // <=>?@ ABCDE
  0x20,0xC0,0x00,0x00,0x70, 0xA0,0x40,0xC0,0x80,0xE0,   // FGHIJ KLMNO                      
  0x60,0xD0,0x40,0x00,0x80, 0x20,0x10,0x60,0x90,0xB0,   // PQRST UVWXY                      
  0xC0,0x00,0x00,0x00,0x00, 0x34,0x00,0x40,0x80,0xA0,   // Z[\]^ _`abc                      
  0x80,0x00,0x20,0xC0,0x00, 0x00,0x70,0xA0,0x40,0xC0,   // defgh ijklm                      
  0x80,0xE0,0x60,0xD0,0x40, 0x00,0x80,0x20,0x10,0x60,   // nopqr stuvw                      
  0x90,0xB0,0xC0,0x00,0x00, 0x00,0x00,0x00              // xyz{| }~                         
};

short morse_code_len [128] = {
  0,0,0,0,0, 0,0,0,0,0,   // ..... .....
  0,0,0,0,0, 0,0,0,0,0,   // ..... .....
  0,0,0,0,0, 0,0,0,0,0,   // ..... .....
  0,0,0,5,6, 0,7,0,5,6,   // .. !" #$%&'
  5,6,0,5,6, 6,6,5,5,5,   // ()*+, -./01
  5,5,5,5,5, 5,5,5,6,6,   // 23456 789:;
  6,5,7,6,0, 2,4,4,3,1,   // <=>?@ ABCDE
  4,3,4,2,4, 3,4,2,2,3,   // FGHIJ KLMNO
  4,4,3,3,1, 3,4,3,4,4,   // PQRST UVWXY
  4,0,0,0,0, 6,0,2,4,4,   // Z[\]^ _`abc
  3,1,4,3,4, 2,4,3,4,2,   // defgh ijklm
  2,3,4,4,3, 3,1,3,4,3,   // nopqr stuvw
  4,4,4,0,0, 0,0,0        // xyz{| }~


};

void dash ()
{
  key_out_on (dash_ticks);
  dash_pause ();
  udelay (3*remainder);
}

void dot ()
{
  key_out_on (dot_ticks);
  dot_pause ();
  udelay (remainder);
}

ssize_t cwkeyer_write (struct file *cwfile,const char *cwtext, size_t cwlen,loff_t *cwinode)
{
int i;
char buf[BUF_SIZE];
unsigned char pos,code,len;
int dotdash;

  if (cwlen > BUF_SIZE) 
    cwlen = BUF_SIZE;
  copy_from_user (buf,cwtext,cwlen);
  for (i=0;i<cwlen;i++) {
    
    if (stopflag != 0){
		i = cwlen;			// stop cw message
		stopflag = 0;		// reset the flag
			break;		
    }
    pos = buf[i] & 0x7F;
    code = morse_code [pos];
    len = morse_code_len [pos];
    for (pos=7;7-pos<len;pos--) {
      dotdash = code&(0x01<<pos);
      if (dotdash) dash ();
      else dot ();
    }
    dash_pause ();
  }
  return cwlen;
}

int cwkeyer_ioctl (struct inode *cwinode, struct file *cwfile, 
                   unsigned int cwcmd, unsigned long cwarg)
{
unsigned int hz,speed;
float fraction;

  switch (cwcmd) {
    case CWTONE : hz = cwarg;

                  if (hz==0) {
                    mute=1;
                    break;
                  }

                  if (hz==1) { 			// set flag to stop message
                    stopflag = 1;
                    break;
                  }
                  else
                   	stopflag = 0;


                  if ((hz < 1) || (hz > 32766))
                    return -EINVAL;
                  if (hz < 200)
                    msweight = hz;
                  else{
                  	mute=0;
                  	count = 1193180 / hz;
                  }
                  break;
    case CWSPEED: speed = cwarg;
                  if ((!speed) || (speed>99))
                    return -EINVAL;
                  dot_ticks = 120/speed; dash_ticks = dot_ticks * 3;
                  fraction = 120.0/speed;
                  fraction = fraction - dot_ticks;
                  remainder =  (int) (fraction * 10000) ;
                  break;
    default	: return -ENOTTY;
  }
  return 0;
}

/* Changes in size of structure in fs.h for kernel 2.4.x - VE3RZ */

struct file_operations cwkeyer_fops = {
  NULL,
  NULL,			// lseek
  NULL,			// read
  cwkeyer_write,
  NULL,			// readdir
  NULL,			// select
  cwkeyer_ioctl,	// ioctl
  NULL,			// mmap
  cwkeyer_open,
  NULL,
  cwkeyer_release,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
   0
};

void cwkeyer_interrupt (int irq, void *dev_id, struct pt_regs *regs)
{
unsigned char icode,dummy;

  icode = inb (IIR(iobase)) & 0x07;
  switch (icode) {
    case 6 : dummy = inb(LSR(iobase));
             break;
    case 4 : dummy = inb(RBR(iobase));
             break;
    case 2 : break;
    default: break;
  }
}

int cwkeyer_init ()
{
  if (check_region(iobase,SER_EXTENT)) {
    printk (KERN_INFO "-EACCES to 0x%x-0x%x\n",iobase,iobase+SER_EXTENT);
    return -EACCES;
  }
  request_region (iobase,SER_EXTENT,"cwkeyer");
  outb (0,IER(iobase));
  if (request_irq(irq,cwkeyer_interrupt,SA_INTERRUPT,"cwkeyer",NULL)) {
    printk (KERN_INFO "-EBUSY to irq %d\n",irq);
    return -EBUSY;
  }
  inDotDash = 0;
  key_out_off ();
  return 0;
}

int init_module(void)
{
int result;

  EXPORT_NO_SYMBOLS;
  result = register_chrdev (major,"cwkeyer",&cwkeyer_fops);
  if (result<0) {
    printk (KERN_WARNING "CWkeyer: Can't register major %d\n",major);
    return result;
  }
  if (cwkeyer_init ()) return 1;
  printk (KERN_INFO "CWkeyer up - major=%d.\n",major);
  return 0;
}

void cleanup_module(void)
{
  free_irq (irq,NULL);
  release_region (iobase,SER_EXTENT);
  unregister_chrdev (major,"cwkeyer");
  printk (KERN_INFO "CWkeyer down.\n");
}