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

#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/ioport.h>

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


#define PWM_BASE    0x220
#define PWM_TIMER_0 0x220
#define PWM_TIMER_1 0x221
#define PWM_TIMER_2 0x222
#define PWM_CONTROL 0x223
#define SERVO_MIN   1041

int pwm_major = 0;
int pwm_servo2;
int pwm_servo1;
int imode;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
int pwm_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", pwm_servo2, pwm_servo1);
  return len;
}
#else
int pwm_read_proc(char *buf, char **start, off_t offset, int len, int unused) {
  len = sprintf(buf, "Servo setting: %i\nThrottle setting: %i\n", pwm_servo2, pwm_servo1);
  return len;
}
#endif

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

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

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

enum pwm_modes {PWM_SERVO=0, PWM_THROTTLE};

static ssize_t pwm_write(struct file *filp,
 const char *buf,
size_t count,
loff_t *offset
) {
    int retval = count;
    unsigned char writeval;
    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>pwm minor: 0x%x\n", mode); 

    switch(mode) {
      case PWM_SERVO:
        while (count--) {
           writeval = *(ptr++);
	   countval = 4 * writeval + SERVO_MIN;
	   /*	   printk("<1>pwm servo2: writing value %i\n", countval);*/
	   outval = countval & 0xff;
	   /*      printk("<1>pwm servo2: writing LSB 0x%.2x\n", outval);*/
	   outb(outval, PWM_TIMER_2);
	   outval = countval >> 8 & 0xff;
	   /* printk("<1>pwm servo2: writing MSB 0x%.2x\n", outval);*/
	   outb(outval, PWM_TIMER_2);
	   pwm_servo2 = countval;
	}
        break;

      case PWM_THROTTLE:
        while (count--) {
           writeval = *(ptr++);
	   countval = 4 * writeval + SERVO_MIN;
	   /* printk("<1>pwm servo1: writing value %i\n", countval);*/
	   outval = countval & 0xff;
           /* printk("<1>pwm servo1: writing LSB 0x%.2x\n", outval);*/
	   outb(outval, PWM_TIMER_1);
	   outval = countval >> 8 & 0xff;
	   /* printk("<1>pwm servo1: 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 pwm_fops = {
    write : pwm_write,
    open : pwm_open,
    release : pwm_release
};

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

    if (result) {
        printk(KERN_INFO "pwm: can't get I/O address 0x%x\n", PWM_BASE);
        return result;
    }
    request_region(PWM_BASE, 32, "pwm");

    result = register_chrdev(pwm_major, "pwm", &pwm_fops);
    if (result < 0) {
        printk(KERN_INFO "pwm: can't get major number\n");
        release_region(PWM_BASE, 32);
        return result;
    }
    if (pwm_major == 0) pwm_major = result;  /* dynamic major number assignment */

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

    /* set up timer 0 to give periodic signals at 100 Hz
     * this signal is derived from the ISA CLK signal
     * which varies from machine to machine
     * on our lab machines, the frequency is eight and one third MHz
     */

    outb(0x34, PWM_CONTROL);
    outb(0xb1, PWM_TIMER_0);
    outb(0x28, PWM_TIMER_0);
    printk("<1>timer 0 initialized...\n");

    /* set up timer 2 to half-scale model aircraft servo position
     * this is a one and one half millisecond pulse
     */

    outb(0xb2, PWM_CONTROL);
    outb(0x1b, PWM_TIMER_2);
    outb(0x06, PWM_TIMER_2);
    printk("<1>timer 2 initialized...\n");
    
    pwm_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);
    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("pwm", S_IRUGO, NULL)) != NULL)
    {
      ent->read_proc = pwm_read_proc;
    }
#else
    proc_register(&proc_root, &pwm_proc_entry);
#endif
    return 0;

}

void cleanup_module(void) {
    unregister_chrdev(pwm_major, "pwm");
    release_region(PWM_BASE, 32);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
    remove_proc_entry("pwm", NULL);
#else
    proc_unregister(&proc_root, pwm_proc_entry.low_ino);
#endif

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

}

