/*
 *  Copyright (C) 2008 Ilya Yanok, Emcraft Systems
 *
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 */

#include <linux/irq.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/io.h>

/*
 * The FPGA supports 9 interrupt sources, which can be routed to 3
 * interrupt request lines of the MPIC. The line to be used can be
 * specified through the third cell of FDT property  "interrupts".
 */

#define SOCRATES_FPGA_NUM_IRQS	9

#define FPGA_PIC_IRQCFG		(0x0)
#define FPGA_PIC_IRQMASK(n)	(0x4 + 0x4 * (n))

#define SOCRATES_FPGA_IRQ_MASK	((1 << SOCRATES_FPGA_NUM_IRQS) - 1)

struct socrates_fpga_irq_info {
	unsigned int irq_line;
	int type;
};

/*
 * Interrupt routing and type table
 *
 * IRQ_TYPE_NONE means the interrupt type is configurable,
 * otherwise it's fixed to the specified value.
 */
static struct socrates_fpga_irq_info fpga_irqs[SOCRATES_FPGA_NUM_IRQS] = {
	[0] = {0, IRQ_TYPE_NONE},
	[1] = {0, IRQ_TYPE_LEVEL_HIGH},
	[2] = {0, IRQ_TYPE_LEVEL_LOW},
	[3] = {0, IRQ_TYPE_NONE},
	[4] = {0, IRQ_TYPE_NONE},
	[5] = {0, IRQ_TYPE_NONE},
	[6] = {0, IRQ_TYPE_NONE},
	[7] = {0, IRQ_TYPE_NONE},
	[8] = {0, IRQ_TYPE_LEVEL_HIGH},
};

static DEFINE_RAW_SPINLOCK(socrates_fpga_pic_lock);

static void __iomem *socrates_fpga_pic_iobase;
static struct irq_domain *socrates_fpga_pic_irq_host;
static unsigned int socrates_fpga_irqs[3];

static inline uint32_t socrates_fpga_pic_read(int reg)
{
	return in_be32(socrates_fpga_pic_iobase + reg);
}

static inline void socrates_fpga_pic_write(int reg, uint32_t val)
{
	out_be32(socrates_fpga_pic_iobase + reg, val);
}

static inline unsigned int socrates_fpga_pic_get_irq(unsigned int irq)
{
	uint32_t cause;
	unsigned long flags;
	int i;

	/* Check irq line routed to the MPIC */
	for (i = 0; i < 3; i++) {
		if (irq == socrates_fpga_irqs[i])
			break;
	}
	if (i == 3)
		return NO_IRQ;

	raw_spin_lock_irqsave(&socrates_fpga_pic_lock, flags);
	cause = socrates_fpga_pic_read(FPGA_PIC_IRQMASK(i));
	raw_spin_unlock_irqrestore(&socrates_fpga_pic_lock, flags);
	for (i = SOCRATES_FPGA_NUM_IRQS - 1; i >= 0; i--) {
		if (cause >> (i + 16))
			break;
	}
	return irq_linear_revmap(socrates_fpga_pic_irq_host,
			(irq_hw_number_t)i);
}

static void socrates_fpga_pic_cascade(struct irq_desc *desc)
{
	struct irq_chip *chip = irq_desc_get_chip(desc);
	unsigned int irq = irq_desc_get_irq(desc);
	unsigned int cascade_irq;

	/*
	 * See if we actually have an interrupt, call generic handling code if
	 * we do.
	 */
	cascade_irq = socrates_fpga_pic_get_irq(irq);

	if (cascade_irq != NO_IRQ)
		generic_handle_irq(cascade_irq);
	chip->irq_eoi(&desc->irq_data);
}

static void socrates_fpga_pic_ack(struct irq_data *d)
{
	unsigned long flags;
	unsigned int irq_line, hwirq = irqd_to_hwirq(d);
	uint32_t mask;

	irq_line = fpga_irqs[hwirq].irq_line;
	raw_spin_lock_irqsave(&socrates_fpga_pic_lock, flags);
	mask = socrates_fpga_pic_read(FPGA_PIC_IRQMASK(irq_line))
		& SOCRATES_FPGA_IRQ_MASK;
	mask |= (1 << (hwirq + 16));
	socrates_fpga_pic_write(FPGA_PIC_IRQMASK(irq_line), mask);
	raw_spin_unlock_irqrestore(&socrates_fpga_pic_lock, flags);
}

static void socrates_fpga_pic_mask(struct irq_data *d)
{
	unsigned long flags;
	unsigned int hwirq = irqd_to_hwirq(d);
	int irq_line;
	u32 mask;

	irq_line = fpga_irqs[hwirq].irq_line;
	raw_spin_lock_irqsave(&socrates_fpga_pic_lock, flags);
	mask = socrates_fpga_pic_read(FPGA_PIC_IRQMASK(irq_line))
		& SOCRATES_FPGA_IRQ_MASK;
	mask &= ~(1 << hwirq);
	socrates_fpga_pic_write(FPGA_PIC_IRQMASK(irq_line), mask);
	raw_spin_unlock_irqrestore(&socrates_fpga_pic_lock, flags);
}

static void socrates_fpga_pic_mask_ack(struct irq_data *d)
{
	unsigned long flags;
	unsigned int hwirq = irqd_to_hwirq(d);
	int irq_line;
	u32 mask;

	irq_line = fpga_irqs[hwirq].irq_line;
	raw_spin_lock_irqsave(&socrates_fpga_pic_lock, flags);
	mask = socrates_fpga_pic_read(FPGA_PIC_IRQMASK(irq_line))
		& SOCRATES_FPGA_IRQ_MASK;
	mask &= ~(1 << hwirq);
	mask |= (1 << (hwirq + 16));
	socrates_fpga_pic_write(FPGA_PIC_IRQMASK(irq_line), mask);
	raw_spin_unlock_irqrestore(&socrates_fpga_pic_lock, flags);
}

static void socrates_fpga_pic_unmask(struct irq_data *d)
{
	unsigned long flags;
	unsigned int hwirq = irqd_to_hwirq(d);
	int irq_line;
	u32 mask;

	irq_line = fpga_irqs[hwirq].irq_line;
	raw_spin_lock_irqsave(&socrates_fpga_pic_lock, flags);
	mask = socrates_fpga_pic_read(FPGA_PIC_IRQMASK(irq_line))
		& SOCRATES_FPGA_IRQ_MASK;
	mask |= (1 << hwirq);
	socrates_fpga_pic_write(FPGA_PIC_IRQMASK(irq_line), mask);
	raw_spin_unlock_irqrestore(&socrates_fpga_pic_lock, flags);
}

static void socrates_fpga_pic_eoi(struct irq_data *d)
{
	unsigned long flags;
	unsigned int hwirq = irqd_to_hwirq(d);
	int irq_line;
	u32 mask;

	irq_line = fpga_irqs[hwirq].irq_line;
	raw_spin_lock_irqsave(&socrates_fpga_pic_lock, flags);
	mask = socrates_fpga_pic_read(FPGA_PIC_IRQMASK(irq_line))
		& SOCRATES_FPGA_IRQ_MASK;
	mask |= (1 << (hwirq + 16));
	socrates_fpga_pic_write(FPGA_PIC_IRQMASK(irq_line), mask);
	raw_spin_unlock_irqrestore(&socrates_fpga_pic_lock, flags);
}

static int socrates_fpga_pic_set_type(struct irq_data *d,
		unsigned int flow_type)
{
	unsigned long flags;
	unsigned int hwirq = irqd_to_hwirq(d);
	int polarity;
	u32 mask;

	if (fpga_irqs[hwirq].type != IRQ_TYPE_NONE)
		return -EINVAL;

	switch (flow_type & IRQ_TYPE_SENSE_MASK) {
	case IRQ_TYPE_LEVEL_HIGH:
		polarity = 1;
		break;
	case IRQ_TYPE_LEVEL_LOW:
		polarity = 0;
		break;
	default:
		return -EINVAL;
	}
	raw_spin_lock_irqsave(&socrates_fpga_pic_lock, flags);
	mask = socrates_fpga_pic_read(FPGA_PIC_IRQCFG);
	if (polarity)
		mask |= (1 << hwirq);
	else
		mask &= ~(1 << hwirq);
	socrates_fpga_pic_write(FPGA_PIC_IRQCFG, mask);
	raw_spin_unlock_irqrestore(&socrates_fpga_pic_lock, flags);
	return 0;
}

static struct irq_chip socrates_fpga_pic_chip = {
	.name		= "FPGA-PIC",
	.irq_ack	= socrates_fpga_pic_ack,
	.irq_mask	= socrates_fpga_pic_mask,
	.irq_mask_ack	= socrates_fpga_pic_mask_ack,
	.irq_unmask	= socrates_fpga_pic_unmask,
	.irq_eoi	= socrates_fpga_pic_eoi,
	.irq_set_type	= socrates_fpga_pic_set_type,
};

static int socrates_fpga_pic_host_map(struct irq_domain *h, unsigned int virq,
		irq_hw_number_t hwirq)
{
	/* All interrupts are LEVEL sensitive */
	irq_set_status_flags(virq, IRQ_LEVEL);
	irq_set_chip_and_handler(virq, &socrates_fpga_pic_chip,
				 handle_fasteoi_irq);

	return 0;
}

static int socrates_fpga_pic_host_xlate(struct irq_domain *h,
		struct device_node *ct,	const u32 *intspec, unsigned int intsize,
		irq_hw_number_t *out_hwirq, unsigned int *out_flags)
{
	struct socrates_fpga_irq_info *fpga_irq = &fpga_irqs[intspec[0]];

	*out_hwirq = intspec[0];
	if  (fpga_irq->type == IRQ_TYPE_NONE) {
		/* type is configurable */
		if (intspec[1] != IRQ_TYPE_LEVEL_LOW &&
		    intspec[1] != IRQ_TYPE_LEVEL_HIGH) {
			pr_warning("FPGA PIC: invalid irq type, "
				   "setting default active low\n");
			*out_flags = IRQ_TYPE_LEVEL_LOW;
		} else {
			*out_flags = intspec[1];
		}
	} else {
		/* type is fixed */
		*out_flags = fpga_irq->type;
	}

	/* Use specified interrupt routing */
	if (intspec[2] <= 2)
		fpga_irq->irq_line = intspec[2];
	else
		pr_warning("FPGA PIC: invalid irq routing\n");

	return 0;
}

static const struct irq_domain_ops socrates_fpga_pic_host_ops = {
	.map    = socrates_fpga_pic_host_map,
	.xlate  = socrates_fpga_pic_host_xlate,
};

void socrates_fpga_pic_init(struct device_node *pic)
{
	unsigned long flags;
	int i;

	/* Setup an irq_domain structure */
	socrates_fpga_pic_irq_host = irq_domain_add_linear(pic,
		    SOCRATES_FPGA_NUM_IRQS, &socrates_fpga_pic_host_ops, NULL);
	if (socrates_fpga_pic_irq_host == NULL) {
		pr_err("FPGA PIC: Unable to allocate host\n");
		return;
	}

	for (i = 0; i < 3; i++) {
		socrates_fpga_irqs[i] = irq_of_parse_and_map(pic, i);
		if (socrates_fpga_irqs[i] == NO_IRQ) {
			pr_warning("FPGA PIC: can't get irq%d.\n", i);
			continue;
		}
		irq_set_chained_handler(socrates_fpga_irqs[i],
					socrates_fpga_pic_cascade);
	}

	socrates_fpga_pic_iobase = of_iomap(pic, 0);

	raw_spin_lock_irqsave(&socrates_fpga_pic_lock, flags);
	socrates_fpga_pic_write(FPGA_PIC_IRQMASK(0),
			SOCRATES_FPGA_IRQ_MASK << 16);
	socrates_fpga_pic_write(FPGA_PIC_IRQMASK(1),
			SOCRATES_FPGA_IRQ_MASK << 16);
	socrates_fpga_pic_write(FPGA_PIC_IRQMASK(2),
			SOCRATES_FPGA_IRQ_MASK << 16);
	raw_spin_unlock_irqrestore(&socrates_fpga_pic_lock, flags);

	pr_info("FPGA PIC: Setting up Socrates FPGA PIC\n");
}
