/*
 * pwm.c -- simple PWM generator based on the 8254
 */

#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>

#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_servo;
int pwm_throttle;


int pwm_read_proc(char *buf, char **start, off_t offset, int len, int unused) {
  len = sprintf(buf, "Servo setting: %i\nThrottle setting: %i", pwm_servo, pwm_throttle);
  return len;
}

struct proc_dir_entry pwm_proc_entry = {
  0,
  3, "pwm",
  S_IFREG | S_IRUGO,
  1, 0, 0,
  0, NULL,
  &pwm_read_proc,
};


int pwm_open(struct inode *inode, struct file *filp) {

    MOD_INC_USE_COUNT;

    return 0;

}

void pwm_release(struct inode *inode, struct file *filp) {

    MOD_DEC_USE_COUNT;

}


enum pwm_modes {PWM_SERVO=0, PWM_THROTTLE};

int pwm_write(struct inode *inode, struct file *filp, const char *buf, int count) {

    int retval = count;
    unsigned char writeval;
    int countval;
    unsigned char outval;
    int mode = (MINOR(inode->i_rdev));
    unsigned char *kbuf=kmalloc(count, GFP_KERNEL), *ptr;

    if (!kbuf) return -ENOMEM;
    memcpy_fromfs(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 model aircraft servo: writing value %i\n", countval);
	    outval = countval & 0xff;
            printk("<1>pwm model aircraft servo: writing LSB 0x%.2x\n", outval);
	    outb(outval, PWM_TIMER_2);
	    outval = countval >> 8 & 0xff;
	    printk("<1>pwm model aircraft servo: writing MSB 0x%.2x\n", outval);
	    outb(outval, PWM_TIMER_2);
	    pwm_servo = countval;
	}
        break;

      case PWM_THROTTLE:
        while (count--) {
            writeval = *(ptr++);
	    countval = 40 * writeval;
	    printk("<1>pwm power throttle: writing value %i\n", countval);
	    outval = countval & 0xff;
            printk("<1>pwm power throttle: writing LSB 0x%.2x\n", outval);
	    outb(outval, PWM_TIMER_1);
	    outval = countval >> 8 & 0xff;
	    printk("<1>pwm power throttle: writing MSB 0x%.2x\n", outval);
	    outb(outval, PWM_TIMER_1);
	    pwm_throttle = countval;
	}
        break;

      default: /* no more modes defined by now */
        retval = -EINVAL;
        break;
    }

    kfree(kbuf);

    return retval;

}


struct file_operations pwm_fops = {
    NULL,          /* pwm_lseek */
    NULL,          /* pwm_read */
    pwm_write,
    NULL,          /* pwm_readdir */
    NULL,          /* pwm_select */
    NULL,          /* pwm_ioctl */
    NULL,          /* pwm_mmap */
    pwm_open,
    pwm_release,
    NULL,          /* pwm_fsync */
    NULL,          /* pwm_fasync */
                   /* nothing more, fill with NULLs */
};

int init_module(void) {

    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_servo = 0x061b;

    /* set up timer 1 to one half duty cycle of 100 Hz period
     *  this would be half-scale power delivery to power device
     */

    outb(0x72, PWM_CONTROL);
    outb(0x58, PWM_TIMER_1);
    outb(0x14, PWM_TIMER_1);
    printk("<1>timer 1 initialized...\n");

    pwm_throttle = 0x1458;

    proc_register_dynamic(&proc_root, &pwm_proc_entry);

    return 0;

}

void cleanup_module(void) {

    unregister_chrdev(pwm_major, "pwm");
    release_region(PWM_BASE, 32);

    proc_unregister(&proc_root, pwm_proc_entry.low_ino);

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

}
