/*
 * squarewave.c -- simple PWM generator for telepoint(tm) system based on the 8254
 *          controls two NARO BB servos, or the like (/dev/squarewave0 and /dev/squarewave1)
 */

#include <linux/module.h>

#include <linux/kernel.h> /* printk() */
#include <linux/malloc.h>
#include <linux/fs.h>     /* everything... */
#include <linux/errno.h>  /* error codes */
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/ioport.h>

#include <asm/segment.h>
#include <asm/io.h>
#include <asm/uaccess.h>


#define SW_BASE    0x260
#define SW_TIMER_0 0x260
#define SW_TIMER_1 0x261
#define SW_TIMER_2 0x262
#define SW_CONTROL 0x263
//#define SERVO_MIN   1041

/* HCL SW_INIT is used to initialize the speaker, this will have to be tweaked with */
#define SW      0xffff
#define SW_MIN  (SW / 200)   // about 330 Hz, somewhere between a C and D
#define SW_INIT (SW_MIN + (SW_MIN >> 1))
//#define SW_INIT (SW >> 2)
#define DIVIDE_FACTOR 1041667

int squarewave_major = 0;
int squarewave_servo2;
int squarewave_servo1;
int imode;

void generate_wave(char *kbuf, int count);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
int squarewave_read_proc(char *buf, char **start, off_t offset, int len, int *eof, void *data) {
  len = sprintf(buf, "Servo setting: %i\nThrottle setting: %i\n", squarewave_servo2, squarewave_servo1);
  return len;
}
#else
int squarewave_read_proc(char *buf, char **start, off_t offset, int len, int unused) {
  len = sprintf(buf, "Servo setting: %i\nThrottle setting: %i\n", squarewave_servo2, squarewave_servo1);
  return len;
}
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0)
struct proc_dir_entry squarewave_proc_entry = {
  0,
  3, "squarewave",
  S_IFREG | S_IRUGO,
  1, 0, 0,
  0, NULL,
  &squarewave_read_proc,
};
#endif

static int squarewave_open(struct inode *inode, struct file *filp) {
    MOD_INC_USE_COUNT;
    return 0;
}

static int squarewave_release(struct inode *inode, struct file *filp) {
    MOD_DEC_USE_COUNT;
    return 0;
}

enum squarewave_modes {SW_CLOCK=0, SW_WAVE};

static ssize_t squarewave_write(struct file *filp,
 const char *buf,
size_t count,
loff_t *offset
) {
    int retval = count;
    //unsigned int writeval;
    //unsigned int countval;
    //unsigned char outval;
    int mode = MINOR(filp->f_dentry->d_inode->i_rdev);
    unsigned char *kbuf=kmalloc(count, GFP_KERNEL), *ptr;

    if (!kbuf) return -ENOMEM;
    /* memcpy_fromfs(kbuf, buf, count);*/ 
    copy_from_user(kbuf, buf, count);
    ptr = kbuf;

    //printk("<1>squarewave minor: 0x%x\n", mode); 

    switch(mode) {
      case SW_CLOCK:
	generate_wave(kbuf, count);
	/*while (count--) {
	  writeval = *(ptr++);
	  printk("<1>timer: servo2: reading in %0x\n", writeval);
	  countval = writeval;
	  printk("<1>timer: writing value %i\n", countval);
	  outval = countval & 0xff;
	  printk("<1>timer: writing LSB 0x%.2x\n", outval);
	  outb(outval, SW_TIMER_0);
	  outval = countval >> 8 & 0xff;
	  printk("<1>timer: writing MSB 0x%.2x\n", outval);
	  outb(outval, SW_TIMER_0);
	*/
	/*
	  writeval = *(ptr++); count--;
	  printk("<1>sq_wave_g: reading in %0x\n", writeval);
	  countval = 4 * writeval;
	  printk("<1>sq_wave_g: writing value %i\n", countval);
	  outval = countval & 0xff;
	  printk("<1>sq_wave_g: writing LSB 0x%.2x\n", outval);
	  outb(outval, SW_TIMER_1);
	  outval = countval >> 8 & 0xff;
	  printk("<1>sq_wave_g: writing MSB 0x%.2x\n", outval);
	  outb(outval, SW_TIMER_1);*/
	//squarewave_servo2 = countval;
	//}
        break;
	
	/*      case SW_THROTTLE:
		while (count--) {
		writeval = *(ptr++);
		countval = 4 * writeval + SERVO_MIN;
		countval = DIVIDE_FACTOR - writeval * ((double)DIVIDE_FACTOR / 255);
		printk("<1>squarewave led: writing value %0x\n", countval);
		outval = countval & 0xff;
		printk("<1>pwm led: writing LSB 0x%.2x\n", outval);
		outb(outval, PWM_TIMER_1);
		outval = (countval >> 8) & 0xff;
		printk("<1>pwm led: writing MSB 0x%.2x\n", outval);
		outb(outval, PWM_TIMER_1);
		pwm_servo1= countval;
		}
		break;
	*/
      default: /* no more modes defined by now */
        retval = -EINVAL;
        break;
    }

    kfree(kbuf);
    return retval;
}

struct file_operations squarewave_fops = {
    write : squarewave_write,
    open : squarewave_open,
    release : squarewave_release
};

int init_module(void) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
    struct proc_dir_entry *ent;
#endif
    int result = check_region(SW_BASE, 32);


    /* HCL changed to SW_BASE */
    if (result) {
        printk(KERN_INFO "squarewave: can't get I/O address 0x%x\n", SW_BASE);
        return result;
    }
    /* HCL changed to SW_BASE */
    request_region(SW_BASE, 32, "squarewave");

    result = register_chrdev(squarewave_major, "squarewave", &squarewave_fops);
    if (result < 0) {
        printk(KERN_INFO "squarewave: can't get major number\n");
	/* HCL changed to SW_BASE */
        release_region(SW_BASE, 32);
        return result;
    }
    if (squarewave_major == 0) squarewave_major = result;  /* dynamic major number assignment */

    printk("<1>squarewave installed...\n");

    /* HCL set up timer 0 for mode 0, interrupt on terminal count down */
    outb(0x30, SW_CONTROL);
/*
    outb(0xb1, PWM_TIMER_0);
    outb(0x28, PWM_TIMER_0);
*/
    /* HCL initialize to highest setting, James and I discussed it and agreed on 1/2 second
       pulse, but that is 5.0+E5 Hz, which is higher than the max allowed in 16 bit, so I
       just set it to max ffff, can change if necessary */
    outb(SW & 0xff, SW_TIMER_0);
    outb((SW >> 8) & 0xff, SW_TIMER_0);

    printk("<1>timer 0 initialized...\n");

    /* HCL this sets up timer 1, will have to initialize this to something,
       not sure what this is right now, so just kept previous settings */
    outb(0x76, SW_CONTROL);
/*
    outb(0x1b, PWM_TIMER_2);
    outb(0x06, PWM_TIMER_2);
*/
    outb(SW_MIN & 0xff , SW_TIMER_1);
    outb((SW_MIN >> 8) & 0xff, SW_TIMER_1);

    printk("<1>timer 1 initialized...\n");
    
    squarewave_servo2 = 0x061b;

    /* set up timer 1 also to center of servo range (as with above servo)
     *  this would be half-way
     */

    /*    outb(0x72, PWM_CONTROL); */
/*
    outb(0x1b, PWM_TIMER_1);
    outb(0x06, PWM_TIMER_1);
*/
    //    outb(LED_INIT & 0xff, PWM_TIMER_1);
    // outb((LED_INIT >> 8) & 0xff, PWM_TIMER_1);
    // printk("<1>timer 1 initialized...\n");

    // pwm_servo1 = 0x061b;

    /*    proc_register_dynamic(&proc_root, &pwm_proc_entry);
     */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
    if ((ent = create_proc_entry("squarewave", S_IRUGO, NULL)) != NULL)
    {
      ent->read_proc = squarewave_read_proc;
    }
#else
    proc_register(&proc_root, &squarewave_proc_entry);
#endif
    return 0;

}

void cleanup_module(void) {
    unregister_chrdev(squarewave_major, "squarewave");
    release_region(SW_BASE, 32);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
    remove_proc_entry("squarewave", NULL);
#else
    proc_unregister(&proc_root, squarewave_proc_entry.low_ino);
#endif

    printk("<1>squarewave removed...\n");

}
 /* JJ convert the initial portion of a string to an integer. */
int atoi(char *s)
{
  int i, n;
  n = 0;
  for (i = 0; s[i] >= '0' && s[i] <= '9'; ++i)
    n = 10 * n + s[i] - '0';

  return n;
}



void generate_wave(char *kbuf, int count) {
  
  //unsigned int writeval;
  unsigned int countval;
  unsigned char outval;
  unsigned int waveval;
  //int i;
/*
  int argc = 1;
  char **argv = (char **) kmalloc(sizeof(char *) *100, GFP_ATOMIC);
  char *p = strtok(kbuf, " ");
  //char *cPtr;
  argv[0] = "";
  kbuf[count] = '\0';
  while (p != NULL) {
    argv[argc] = p;
    argc++;
    p = strtok(NULL, " ");
  }
*/
  char *p;
  kbuf[count] = '\0';

  p = strtok(kbuf, " "); 
  if (p != NULL)
    countval = atoi(p);
  else
    countval = 1;

  p = strtok(NULL, " "); 
  if (p != NULL)
    waveval = atoi(p);
  else
    waveval = 350;
  
  //printk("<1>timer: servo2: reading in %0x\n", writeval);
  //countval = atoi(argv[1]);
  printk("<1>timer: writing value %i\n", countval);
  outval = countval & 0xff;
  printk("<1>timer: writing LSB 0x%.2x\n", outval);
  outb(outval, SW_TIMER_0);
  outval = countval >> 8 & 0xff;
  printk("<1>timer: writing MSB 0x%.2x\n", outval);
  outb(outval, SW_TIMER_0);
	
  //waveval = atoi(argv[2]);
  waveval = DIVIDE_FACTOR*(1.0/waveval);
  printk("<1>sq_wave_g: writing value %i\n", waveval);
  outval = waveval & 0xff;
  printk("<1>sq_wave_g: writing LSB 0x%.2x\n", outval);
  outb(outval, SW_TIMER_1);
  outval = waveval >> 8 & 0xff;
  printk("<1>sq_wave_g: writing MSB 0x%.2x\n", outval);
  outb(outval, SW_TIMER_1);

  udelay(countval);
  outb(0x00, SW_TIMER_1);
  outb(0x00, SW_TIMER_1);
}

