/*
   Some interesting bits of this code were taken from lirc_serial driver of
   project LIRC.
   Operation is very simplified, every time serial port gets a pulse, we send
   it down the input layer (which does the timestamping for us).
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/config.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/kernel.h>
#include <linux/serial_reg.h>
#include <linux/time.h>
#include <linux/input.h>

#include <asm/io.h>

#define DRV "homebrew_ir"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Domen Puncer <domen@coderock.org>");

struct homebrew_ir {
	struct list_head list;
	int io;
	int irq;
	struct input_dev input;
};

static const int pin_change = UART_MSR_DDSR | UART_MSR_DDCD;

/*static int io;
static int irq;*/
static int io = 0x2f8;
static int irq = 3;

static LIST_HEAD(homebrew_list);


static irqreturn_t irq_handler(int i, void *data, struct pt_regs *regs)
{
	struct homebrew_ir *hir = data;

	if ((inb(hir->io + UART_IIR) & UART_IIR_NO_INT))
		return IRQ_RETVAL(IRQ_NONE);

	if (inb(hir->io + UART_MSR) & pin_change) {
		input_event(&hir->input, EV_MSC, MSC_RAW, 0);
		input_sync(&hir->input);
	}

	return IRQ_RETVAL(IRQ_HANDLED);
}

static void homebrew_ir_probe(void)
{
	struct homebrew_ir *hir = kmalloc(sizeof(*hir), GFP_KERNEL);
	if (!hir)
		return;
	*hir = (struct homebrew_ir) {
		.io = io,
		.irq = irq,
	};
	INIT_LIST_HEAD(&hir->list);
	init_input_dev(&hir->input);
	hir->input.name = DRV;
	hir->input.evbit[0] = BIT(EV_KEY) | BIT(EV_MSC);
	hir->input.mscbit[0] = BIT(MSC_RAW);

	input_register_device(&hir->input);

	if (request_region(hir->io, 8, DRV) == NULL) {
		printk(KERN_ERR DRV ": port 0x%x already in use\n", hir->io);
		goto kfree;
	}

	outb(inb(hir->io + UART_LCR) & ~UART_LCR_DLAB, hir->io + UART_LCR);
	outb(inb(hir->io + UART_IER) &
		~(UART_IER_MSI | UART_IER_RLSI | UART_IER_THRI |
	       UART_IER_RDI), hir->io + UART_IER);
	outb(UART_MCR_RTS | UART_MCR_OUT2, hir->io + UART_MCR);

	if (request_irq(hir->irq, irq_handler, SA_INTERRUPT | SA_SHIRQ, DRV, hir)) {
		printk(KERN_ERR DRV ": irq %i already in use\n", hir->irq);
		goto release_region;
	}

	outb(inb(hir->io + UART_IER) | UART_IER_MSI, hir->io + UART_IER);

	list_add_tail(&hir->list, &homebrew_list);
	printk(KERN_INFO DRV ": device added, io: 0x%x, irq: %i\n", hir->io, hir->irq);

	return;

 release_region:
	release_region(hir->io, 8);
 kfree:
	input_unregister_device(&hir->input);
	kfree(hir);
}

static int homebrew_ir_probe_set(const char *val, struct kernel_param *kp)
{
	if (io && irq)
		homebrew_ir_probe();
	return 0;
}

static int __init homebrew_ir_init(void)
{
	return homebrew_ir_probe_set(NULL, NULL);
}

static void __exit homebrew_ir_exit(void)
{
	struct homebrew_ir *hir;
	list_for_each_entry(hir, &homebrew_list, list) {
		outb(inb(hir->io + UART_IER) &
		     ~(UART_IER_MSI | UART_IER_RLSI | UART_IER_THRI |
		       UART_IER_RDI), hir->io + UART_IER);

		free_irq(hir->irq, hir);
		release_region(hir->io, 8);
		input_unregister_device(&hir->input);
	}
}

module_init(homebrew_ir_init);
module_exit(homebrew_ir_exit);

module_param(io, int, 0644);
MODULE_PARM_DESC(io, "I/O address base (0x3f8 or 0x2f8)");
module_param(irq, int, 0644);
MODULE_PARM_DESC(irq, "Interrupt (4 or 3)");
module_param_call(probe, homebrew_ir_probe_set, NULL, NULL, 0200);
MODULE_PARM_DESC(probe, "Write in here, when you set up io and irq, and are ready to add");
