/*
 * NXP PNX833x Infrared blaster driver
 *
 * (C) 2010 BrightSign LLC
 *
 * Authors:
 *   Mike Crowe <mcrowe@brightsign.biz>
 *
 * This driver is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published
 * by the Free Software Foundation; either version 2 of the License,
 * or (at your option) any later version.
 *
 * This driver is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this driver; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA. Alternatively visit http://www.gnu.org/licenses/.
 *
 */

#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <asm/mach-pnx833x/gpio.h>
#include <pnx833x.h>

//#define debug printk
#define debug(...) do {} while(0)

//#define debugi printk
#define debugi(...) do {} while(0)

#define IRB_IRQ PNX8335_PIC_IR1_IRQ_INT
#define IRB_GPIO_PIN 40
#define IRB1_REGS_BASE 0x17e55000
#define PNX833X_IRB_REG(off) PNX833X_REG(0x55000 + (off))

#define IRB_CONF PNX833X_IRB_REG(0x00)
#define IRB_CARRIER PNX833X_IRB_REG(0x04)
#define IRB_FIFO_IN PNX833X_IRB_REG(0x08)
#define IRB_STATUS PNX833X_IRB_REG(0x0c)
#define IRB_CMD PNX833X_IRB_REG(0x10)
#define IR_INT_STA PNX833X_IRB_REG(0xfe0)
#define IR_INT_ENA PNX833X_IRB_REG(0xfe4)
#define IR_INT_CLR PNX833X_IRB_REG(0xfe8)
#define IR_INT_SET PNX833X_IRB_REG(0xfec)
#define IR_POWERDOWN PNX833X_IRB_REG(0xff4)

// IRB_CMD
#define IRB_ENA (1<<0)
#define IRB_DIS (1<<1)
#define IRB_START (1<<2)
#define IRB_FIFO_RST (1<<3)

// IRB_CONF
#define IRB_ENV_INI (1<<0)
#define IRB_MODE (1<<1)
#define IRB_OUT(x) (((x) & 3) << 2)
#define IRB_NO_CAR (1<<4)
#define IRB_CAR_INI (1<<5)

// IRB_CARRIER
#define IRB_CTU(x) ((x) & 0xffff)
#define IRB_CLOW(x) (((x) & 7) << 16)
#define IRB_CHIGH(x) (((x) & 3) << 19)

// IRB_FIFO_IN
#define IRB_ENV_LAST (1<<13)
#define IRB_ENV_INT (1<<12)
#define IRB_ENV(x) ((x) & 0xfff)

// IRB_STATUS
#define IRB_RUN_ST (1<<8)
#define IRB_ENA_ST (1<<7)
#define IRB_FIFO_EMPTY (1<<6)
#define IRB_FIFO_FULL (1<<5)
#define IRB_FIFO_LVL(x) ((x) & 0x1f)

// IR_INT_STA
#define BLA_ENV_START (1<<0)
#define BLA_ENV_LAST (1<<1)
#define BLA_FIFO_UFL (1<<2)

// IR_INT_ENA
#define BLA_ENV_START_ENA (1<<0)
#define BLA_ENV_LAST_ENA (1<<1)
#define BLA_FIFO_UFL_ENA (1<<2)

#define IRB_RAISE_INTERRUPT_AT 11
#define IRB_FIFO_SIZE 16

// How long we have to be silent for between IR sequences to ensure
// that the receiver can differentiate them.
#define IRB_QUIET_JIFFIES HZ/10

struct irblast_sequence {
	wait_queue_head_t complete_wq;
	atomic_t complete;
	int pos;
	int len;
	u16 sequence[0];
};

struct irblast_dev {
	spinlock_t lock;
	wait_queue_head_t available_wq;
	struct irblast_sequence *active_sequence;
	atomic_t open_count;
	unsigned long next_send_time;
};

struct irblast_dev irb_dev;

struct irblast_sequence *alloc_sequence_user(const char __user *input_sequence, int len)
{
	int i;
	struct irblast_sequence *s;
	
	if (!access_ok(VERIFY_READ, input_sequence, len * sizeof(u32)))
		return ERR_PTR(-EFAULT);

	s = kmalloc(sizeof(struct irblast_sequence) + len * sizeof(u16), GFP_KERNEL);
	if (!s)
		return ERR_PTR(-ENOMEM);

	for(i = 0; i < len; ++i) {
		u32 input;
		int result = __get_user(input, ((const u32 __user *)input_sequence) + i);
		if (unlikely(result)) {
			kfree(s);
			return ERR_PTR(result);
		}
		
		s->sequence[i] = (38 * (input & 0xffffff)) / 1000;
	}

	init_waitqueue_head(&s->complete_wq);
	atomic_set(&s->complete, 0);
	s->pos = 0;
	s->len = len;

	debug("Sequence of length %d allocated at %p\n", len, s);

	return s;
}

void free_sequence(struct irblast_sequence *s)
{
	debug("Freeing sequence at %p\n", s);
	kfree(s);
}

static void irblast_enqueue(struct irblast_sequence *sequence, int fifo_pos)
{
	debugi("irblast_enqueue seqpos=%d, seqlen=%d, fifo_pos=%d\n", sequence->pos, sequence->len, fifo_pos);
	while ((sequence->pos < sequence->len) && (fifo_pos < IRB_FIFO_SIZE)) {
		unsigned long env = sequence->sequence[sequence->pos++];
		if (sequence->pos == sequence->len)
			env |= IRB_ENV_LAST;
		else if (fifo_pos == IRB_RAISE_INTERRUPT_AT)
			env |= IRB_ENV_INT;

		debugi("FIFO(%d): %04lx\n", sequence->pos - 1, env);
		IRB_FIFO_IN = env;
		++fifo_pos;
	}
}

static irqreturn_t irblast_interrupt(int irq, void *dev_instance)
{
	struct irblast_dev *dev = dev_instance;
	struct irblast_sequence *sequence;
	unsigned long flags;
	unsigned long interrupt_status = IR_INT_STA;
	
	debugi("Enter irblast_interrupt %x\n", interrupt_status);

	if (interrupt_status & (BLA_FIFO_UFL | BLA_ENV_START | BLA_ENV_LAST)) {
		spin_lock_irqsave(&dev->lock, flags);
		sequence = dev->active_sequence;
		
		// Acknowledge all the interrupts. If we don't then we risk
		// looping forever with cancellation.
		IR_INT_CLR = (BLA_FIFO_UFL | BLA_ENV_START | BLA_ENV_LAST);
		
		// We might have been cancelled
		if (sequence) {
			if (interrupt_status & BLA_FIFO_UFL) {
				// Acknowledge anything that's going.
				pr_err("irblast underflow\n");
				atomic_set(&sequence->complete, -EIO);
				IRB_CMD = IRB_FIFO_RST;
				wake_up(&sequence->complete_wq);
			} else if (interrupt_status & BLA_ENV_LAST) {
				IR_INT_CLR = BLA_ENV_LAST;
				debugi("BLA_ENV_LAST\n");
				atomic_set(&sequence->complete, 1);
				dev->next_send_time = jiffies + IRB_QUIET_JIFFIES;
				wake_up(&sequence->complete_wq);
			} else if (interrupt_status & BLA_ENV_START) {
				IR_INT_CLR = BLA_ENV_START;
				debugi("BLA_ENV_START\n");
				irblast_enqueue(sequence, IRB_FIFO_LVL(IRB_STATUS));
			}
		}
		
		spin_unlock_irqrestore(&dev->lock, flags);
		
		debugi("Leave irblast_interrupt\n");
		return IRQ_HANDLED;
	} else {
		debugi("Lerave irblast_interrupt not for us\n");
		return IRQ_NONE;
	}
}

static int irblast_start(void)
{
        int ret = request_irq(IRB_IRQ, irblast_interrupt, 0, "irblast", &irb_dev);
        if (ret) {
		IRB_CMD = IRB_DIS;
                return ret;
        }

	gpio_select_function_alt(IRB_GPIO_PIN);
	
	IRB_CMD = IRB_ENA;
	IRB_CARRIER = IRB_CTU(0x1a5) | IRB_CLOW(1);
	IRB_CONF = IRB_CAR_INI | IRB_OUT(1);
			
	// Enable the interrupts
	IR_INT_ENA = BLA_ENV_START_ENA | BLA_ENV_LAST_ENA | BLA_FIFO_UFL_ENA;
	
	return 0;
}

static void irblast_stop(void)
{
	IR_INT_ENA = 0;
	IRB_CMD = IRB_DIS;
	free_irq(IRB_IRQ, NULL);
}

static int irblast_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &irb_dev;
	return 0;
}

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

static ssize_t irblast_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
	struct irblast_dev *dev = filp->private_data;
	struct irblast_sequence *sequence;
	int queued = 0;
	int ret;
	
	count /= 4;

	if (count > 256)
		return -E2BIG;

	sequence = alloc_sequence_user(buf, count);
	if (IS_ERR(sequence))
		return PTR_ERR(sequence);

	debug("Waiting for irblast device to be free\n");
	do {
		DEFINE_WAIT(wait);
		prepare_to_wait(&dev->available_wq, &wait, TASK_INTERRUPTIBLE);
		
		spin_lock_irq(&dev->lock);
		if (dev->active_sequence) {
			spin_unlock_irq(&dev->lock);
			debug("irblast already active\n");
			schedule();
		} else {
			// This method may cause starvation but we
			// don't expect a lot of contention so that
			// shouldn't be a problem.
			long wait_for = (long)dev->next_send_time - (long)jiffies;
			if (wait_for > 0) {
				spin_unlock_irq(&dev->lock);
				debug("irblast in quiet period until %lu\n", dev->next_send_time);
				schedule_timeout(wait_for);
			} else {
				debugi("irblast acquired\n");
				// It's not in use and we're locked against anyone else.
				dev->active_sequence = sequence;
				irblast_enqueue(sequence, 0);
				queued = 1;
				IRB_CMD = IRB_START;
				debugi("Started %lx\n", IRB_STATUS);
				spin_unlock_irq(&dev->lock);
			}
		}
		finish_wait(&dev->available_wq, &wait);

		if (!queued && signal_pending(current)) {
			free_sequence(sequence);
			return -ERESTARTSYS;
		}
	} while (!queued);

	debug("Waiting for IR to go out %lx.\n", IRB_STATUS);

	// We've set it all going. Now let's wait for it to go out.
	if (wait_event_interruptible(sequence->complete_wq, atomic_read(&sequence->complete))) {
		// Now we're in a quandry. We've started it so
		// we'd better stop it and detach it before
		// freeing it.
		spin_lock_irq(&dev->lock);
		dev->active_sequence = NULL;
		IRB_CMD = IRB_FIFO_RST;
		spin_unlock_irq(&dev->lock);

		ret = -ERESTARTSYS;
	} else {
		spin_lock_irq(&dev->lock);
		dev->active_sequence = NULL;
		spin_unlock_irq(&dev->lock);

		ret = atomic_read(&sequence->complete);
		if (ret > 0)
			ret = count * 4;
	}

	wake_up(&dev->available_wq);

	debug("Finished %lx\n", IRB_STATUS);

	free_sequence(sequence);
	return ret;
}

static const struct file_operations irblast_fops = {
	.open = irblast_open,
	.release = irblast_release,
	.write = irblast_write
};

static struct miscdevice irblast_dev = {
        .minor = MISC_DYNAMIC_MINOR,
        .name = "irblast",
        .fops = &irblast_fops
};

static int __devinit irblast_probe(struct platform_device *dev)
{
	spin_lock_init(&irb_dev.lock);
	init_waitqueue_head(&irb_dev.available_wq);
	irb_dev.next_send_time = jiffies;
	
	irblast_start();
	return misc_register(&irblast_dev);
}

static int __devexit irblast_remove(struct platform_device *dev)
{
	irblast_stop();
	misc_deregister(&irblast_dev);
	return 0;
}

static struct platform_driver irblast_platform_driver = {
        .driver         = {
                .name   = "pnx833x-irblast",
                .owner  = THIS_MODULE,
        },
        .probe          = irblast_probe,
        .remove         = __devexit_p(irblast_remove),
};

static struct platform_device *irblast_platform_device;

static int __init irblast_init(void)
{
	int err;
	
	err = platform_driver_register(&irblast_platform_driver);
	if (err == 0) {
		irblast_platform_device = platform_device_alloc("pnx833x-irblast", -1);
		if (irblast_platform_device) {
			err = platform_device_add(irblast_platform_device);
			if (err == 0) {
				return 0;
			} else {
				platform_device_put(irblast_platform_device);
			}
		} else {
			err = -ENOMEM;
		}
		
		platform_driver_unregister(&irblast_platform_driver);
	}
	return err;
}

static void __exit irblast_exit(void)
{
	platform_device_del(irblast_platform_device);
	platform_driver_unregister(&irblast_platform_driver);
}

module_init(irblast_init);
module_exit(irblast_exit);

