/*
 * Parallel port driver for CD controler
 * (for linux 2.4.24)
 */

/* module prototype */
#define MODULE
#define __KERNEL__

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/string.h>
#include <asm/uaccess.h>
#define DEV_MAJOR 60

/* I/O */
#include <linux/types.h>
#include <linux/config.h>
#include <asm/system.h>
#include <asm/io.h>
#define OUT_PORT 0x378
#define MONITOR_MAX 6
/*
addr  bit  pin  use
0x378  0   (2)  monitor0 1   1  1
0x378  1   (3)  monitor1 2   2  2
0x378  2   (4)  monitor2 4   4  4
0x378  3   (5)  monitor3 8   8  8
0x378  4   (6)  monitor4 1  16 10 
0x378  5   (7)  monitor5 2  32 20
0x378  6   (8)  /CS      4  64 40
0x378  7   (9)  /CLK     8 128 80
*/

#define IN_PORT 0x379
/*
addr  bit  pin  use
0x379  0   (-)  -        1   1
0x379  1   (-)  -        2   2
0x379  2   (-)  -        4   4
0x379  3  (15)  volume   8   8
0x379  4  (13)  command2 1  16
0x379  5  (12)  command1 2  32
0x379  6   (-)  -        4  64
0x379  7   (-)  -        8 128
*/

void my_sleep(void){
  schedule_timeout(1000 * 1000);
}

static char *devname = "mylp0";
static char inbit, outbit;
MODULE_PARM(DEV_MAJOR, "i");
MODULE_PARM(devname, "s");

static int mylp0_open(struct inode * inode, struct file * file){
  outbit = 0xc0;
  outb(outbit, OUT_PORT);
  inbit = inb(IN_PORT);
  MOD_INC_USE_COUNT;
  return 0;
}

static int mylp0_close(struct inode * inode, struct file * file){
  printk("mylp0 closed\n");
  MOD_DEC_USE_COUNT;
  return 0;
}

static int mylp0_write(struct file * file, const char * buff, size_t count, loff_t *pos){
  char c;
  c = outbit & 0xc0;
  c |= buff[0];
  outb(c, OUT_PORT);
  return 0;
}

static int mylp0_read(struct file * file, char * buff, size_t count, loff_t *pos){
  int i;
  char command, ca[3];
  int volume;
  /* 1st clock */
  outbit |= 0xc0;
  outb(outbit, OUT_PORT);
  my_sleep();
  for(i = 0; i < 7; i++){
    outbit &= 0x7f;        // up
    outb(outbit, OUT_PORT);
    my_sleep();
    
    outbit |= 0x80;        // down
    outb(outbit, OUT_PORT);
    my_sleep();
  }
  inbit = inb(IN_PORT);
  command = inbit & 0x30;
  command = command >> 4;
  volume = 0;
  for(i = 0; i < 8; i++){
    char c;
    outbit &= 0x7f;        // up
    outb(outbit, OUT_PORT);
    inbit = inb(IN_PORT);
    c = inbit & 0x08;
    volume = (volume << 1) + (c >> 3); 
    my_sleep();
    outbit |= 0x80;        // down
    outb(outbit, OUT_PORT);
    my_sleep();
  }

  for(i = 0; i < 4; i++){
    outbit &= 0x7f;        // up clk
    outb(outbit, OUT_PORT);
    my_sleep();
    outbit |= 0x80;        // down clk
    outb(outbit, OUT_PORT);
    my_sleep();
  }
  
  outbit &= 0x3f;   //up clk, cs
  outb(outbit, OUT_PORT);
  ca[0] = command;
  ca[1] = volume - 128;
  ca[2] = '\0';
  copy_to_user(buff, ca, 3);
  return 3;
}

static struct file_operations mylp0_fops = {
  NULL, //	struct module *owner;
  NULL, //	loff_t (*llseek) (struct file *, loff_t, int);
  mylp0_read,  //	ssize_t (*read) (struct file *, char *, size_t, loff_t *);
  mylp0_write, //	ssize_t (*write)(struct file *, const char *, size_t, loff_t *);
  NULL, //	int (*readdir) (struct file *, void *, filldir_t);
  NULL, //	unsigned int (*poll) (struct file *, struct poll_table_struct *);
  NULL, //	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
  NULL, //	int (*mmap) (struct file *, struct vm_area_struct *);
  mylp0_open, //	int (*open) (struct inode *, struct file *);
  NULL, //	int (*flush) (struct file *);
  mylp0_close, //	int (*release) (struct inode *, struct file *);
  NULL, //	int (*fsync) (struct file *, struct dentry *, int datasync);
  NULL, //	int (*fasync) (int, struct file *, int);
  NULL, //	int (*lock) (struct file *, int, struct file_lock *);
  NULL, //	ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
  NULL, //	ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
  NULL, //	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
  NULL //	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
};


// ÅÐÏ¿
int init_module(void){
  if(register_chrdev(DEV_MAJOR,devname,&mylp0_fops)){
    printk("device registration error\n");
    return -EBUSY;
  }
  return 0;
}

// ½üµî
void cleanup_module(void){
  if (unregister_chrdev(DEV_MAJOR,devname)){
    printk ("unregister_chrdev failed\n");
  }
};

/*
gcc -c mylp0.c -Wall -Wstrict-prototypes -O -pipe -march=i486
# mknod /dev/mylp0 c 60 0
# chmod a+rw /dev/mylp0
# insmod mylp0
# rmmod mylp0
*/

