#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/init.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h>
#include <linux/cdev.h>


#define BOUTON_GAUCHE 137
#define BOUTON_DROITE 138
#define BOUTON_CENTRE 139

static dev_t myDrive_dev = 0;
static struct class* myDrive_class;
static struct cdev myDrive_cdev;

int val_gauche=1;
int val_droite=1;
int val_bouton=1;
int irq_left=1;
int irq_right=1;
int irq_bouton=1;
char joy_read[3];

// Module can take a parameter when loaded, eg. "insmod mylab.ko debug=1"
static int debug;
module_param(debug, int, 0);

// Operation prototypes
static int     dev_open(struct inode *, struct file *);
static ssize_t dev_read(struct file *, char *, size_t, loff_t *);
static int     dev_release(struct inode *, struct file *);
static int check_destruct(int val);
// The driver's file operations
static struct file_operations fops = {
	.owner = THIS_MODULE,
	.open = dev_open,
	.read = dev_read,
	.release = dev_release,
};

static irqreturn_t myDrive_left_irq_handler(int irq, void *dev_id);
static irqreturn_t myDrive_right_irq_handler(int irq, void *dev_id);
static irqreturn_t myDrive_button_irq_handler(int irq, void *dev_id);

/**
 * Driver initialization code.
 */
static int __init myDrive_dev_init(void) {
	// 1) Allocate the character device (major number)
	if(alloc_chrdev_region(&myDrive_dev,1,1, "myDrive")<0){
		unregister_chrdev_region(myDrive_dev,1);
		return -1;
	}
	// 2) Initialize cdev structure
	cdev_init(&myDrive_cdev,&fops);
	// 3) Add the character device
	if(cdev_add(&myDrive_cdev,myDrive_dev,1)<0){
		unregister_chrdev_region(myDrive_dev,1);
		cdev_del(&myDrive_cdev);
		return -1;
	}
	// 4) Create the class
	myDrive_class = class_create(THIS_MODULE,"myDrive");
	if(myDrive_class == NULL){
		unregister_chrdev_region(myDrive_dev,1);
		cdev_del(&myDrive_cdev);
		class_destroy(myDrive_class);
		return -1;
	}
	// 5) Create the device
	struct device *myLabDevice = device_create (myDrive_class, NULL, myDrive_dev, NULL,"myDrive");
	// 6) Request the necessary GPIOs

	if(check_destruct(gpio_request_one(BOUTON_GAUCHE, GPIOF_DIR_IN,"Left"))){
		return -1;
	}
	if(check_destruct(gpio_request_one(BOUTON_DROITE, GPIOF_DIR_IN,"Right"))){
		return -1;
	}
	if(check_destruct(gpio_request_one(BOUTON_CENTRE, GPIOF_DIR_IN,"Button"))){
		return -1;
	}

	gpio_direction_input (BOUTON_GAUCHE);
	gpio_direction_input (BOUTON_DROITE);
	gpio_direction_input (BOUTON_CENTRE);

	// 7) Register an IRQ handler per GPIO
	irq_left =  gpio_to_irq(BOUTON_GAUCHE);
	irq_right =  gpio_to_irq(BOUTON_DROITE);
	irq_bouton =  gpio_to_irq(BOUTON_CENTRE);

	if(check_destruct(request_threaded_irq(irq_left,myDrive_left_irq_handler,NULL,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,"Left",NULL))){
		return -1;
	}
	if(check_destruct(request_threaded_irq(irq_right,myDrive_right_irq_handler,NULL,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,"Right",NULL))){
		return -1;
	}
	if(check_destruct(request_threaded_irq(irq_bouton,myDrive_button_irq_handler,NULL,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,"Button",NULL))){
		return -1;
	}

	val_gauche=gpio_get_value(BOUTON_GAUCHE);
	val_droite=gpio_get_value(BOUTON_DROITE);
	val_bouton=gpio_get_value(BOUTON_CENTRE);

	printk(KERN_INFO "myDrive: driver initialized\n");
	return 0;
	}

static int check_destruct(int val){
	if(val<0){
		unregister_chrdev_region(myDrive_dev,1);
		cdev_del(&myDrive_cdev);
		class_destroy(myDrive_class);
		device_destroy(myDrive_class,myDrive_dev);
		return 1;
	}
	return 0;
}
/**
 * This function is called when the module is unloaded.
 * Cleanup all gpios and unregister the driver.
 */
static void __exit myDrive_dev_exit(void) {
	// 1) Destroy the device
	device_destroy(myDrive_class,myDrive_dev);
	// 2) Destroy the class
	class_destroy(myDrive_class);
	// 3) Delete the cdev structure
	cdev_del(&myDrive_cdev);
	// 4) Unregister the character device (deallocate)
	unregister_chrdev_region(myDrive_dev,1);
	// 5) Free the IRQs
	irq_left =  gpio_to_irq(BOUTON_GAUCHE);
	irq_right =  gpio_to_irq(BOUTON_DROITE);
	irq_bouton =  gpio_to_irq(BOUTON_CENTRE);
	free_irq(irq_left,NULL);
	free_irq(irq_right,NULL);
	free_irq(irq_bouton,NULL);
	// 6) Free the GPIOs
	gpio_free(BOUTON_GAUCHE);
	gpio_free(BOUTON_DROITE);
	gpio_free(BOUTON_CENTRE);

	printk(KERN_INFO "myDrive: driver destroyed\n");
}

/**
 * Open operation
 */
static int dev_open(struct inode *inodep, struct file *filep) {
	return 0;
}

/**
 * Read operation
 */
static ssize_t dev_read( struct file *filep, char *buffer, size_t len, loff_t *offset) {
	// - Read the joystick state (ie. write it to the user space buffer)
	//printk(KERN_INFO "%d %d %d\n",val_gauche,val_droite,val_bouton);
	joy_read[0]=val_gauche;
	joy_read[1]=val_droite;
	joy_read[2]=val_bouton;
	return copy_to_user(buffer, joy_read, 3);
}

/**
 * Release operation
 */
static int dev_release(struct inode *inodep, struct file *filep) {
	// - Nothing...
	return 0;
}

/**
 * Joystick button IRQ handler
 */
static irqreturn_t myDrive_button_irq_handler(int irq, void *dev_id) {
	// - Update joystick state when center button is pressed
	val_bouton=gpio_get_value(BOUTON_CENTRE);
	return (irqreturn_t) IRQ_HANDLED;  // Announce that the IRQ has been handled correctly
}

/**
 * Joystick left IRQ handler
 */
static irqreturn_t myDrive_left_irq_handler(int irq, void *dev_id) {
	// - Update joystick state when left button is pressed
	val_gauche=gpio_get_value(BOUTON_GAUCHE);
	return (irqreturn_t) IRQ_HANDLED;  // Announce that the IRQ has been handled correctly
}

/**
 * Joystick right IRQ handler
 */
static irqreturn_t myDrive_right_irq_handler(int irq, void *dev_id) {
	// - Update joystick state when right button is pressed
	val_droite=gpio_get_value(BOUTON_DROITE);
	return (irqreturn_t) IRQ_HANDLED;  // Announce that the IRQ has been handled correctly
}

/**
 * A module must use the module_init() module_exit() macros from include/linux/init.h, which
 *  identify the initialization function at insertion time and the cleanup function (as
 *  listed above)
 */
module_init(myDrive_dev_init);
module_exit(myDrive_dev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jaime Coder <jaime.coder@hesge.ch>");
MODULE_DESCRIPTION("skeleton module");
MODULE_VERSION("0.1");
