From 005dccd6eea8b06f9f8a72394d62fc5d144c1c70 Mon Sep 17 00:00:00 2001
From: Alessandro Rubini <rubini@gnudd.com>
Date: Thu, 19 Jul 2012 13:25:12 +0200
Subject: [PATCH] kernel: added irq support in the ops

---
 kernel/spec-fmc.c | 134 ++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 125 insertions(+), 9 deletions(-)

diff --git a/kernel/spec-fmc.c b/kernel/spec-fmc.c
index f98e6bd..60a5e4c 100644
--- a/kernel/spec-fmc.c
+++ b/kernel/spec-fmc.c
@@ -9,8 +9,116 @@
  */
 #include <linux/slab.h>
 #include <linux/fmc.h>
+#include <linux/interrupt.h>
 #include "spec.h"
 
+/* The main role of this file is offering the fmc_operations for the spec */
+
+static int spec_irq_request(struct fmc_device *fmc, irq_handler_t handler,
+			    char *name, int flags)
+{
+	return request_irq(fmc->irq, handler, flags, name, fmc);
+}
+
+static void spec_irq_ack(struct fmc_device *fmc)
+{
+	struct spec_dev *spec = fmc->carrier_data;
+
+	/*
+	 * Note: we only support gpio interrupts here, i.e. the
+	 * 0x814 (INT_STAT) register is expected to only have bit 15 set.
+	 * We also accept software-generated irq, but they need no ack.
+	 */
+	gennum_readl(spec, GNGPIO_INT_STATUS);
+}
+
+static int spec_irq_free(struct fmc_device *fmc)
+{
+	free_irq(fmc->irq, fmc);
+	return 0;
+}
+
+static struct fmc_operations spec_fmc_operations = {
+	/* FIXME: readl/writel */
+	/* FIXME: reprogram */
+	.irq_request =		spec_irq_request,
+	.irq_ack =		spec_irq_ack,
+	.irq_free =		spec_irq_free,
+	/* FIXME: eeprom */
+};
+
+/*
+ * Since interrupts are a hairy thing with the gennum, make a test run
+ * of interrupt handling using its own internal "software interrupt"
+ */
+
+static irqreturn_t spec_test_handler(int irq, void *dev_id)
+{
+	struct fmc_device *fmc = dev_id;
+	struct spec_dev *spec = fmc->carrier_data;
+
+	printk("got %i!\n", irq);
+	spec->irq_count++;
+	complete(&spec->compl);
+	fmc->op->irq_ack(fmc);
+	return IRQ_HANDLED;
+}
+
+static int spec_irq_init(struct fmc_device *fmc)
+{
+	struct spec_dev *spec = fmc->carrier_data;
+	uint32_t value;
+	int i;
+
+	/*
+	 * Enable multiple-msi to work around a chip design bug.
+	 * See http://blog.tftechpages.com/?p=595
+	 */
+	value = gennum_readl(spec, GNPPCI_MSI_CONTROL);
+	if ((value & 0x810000) != 0x810000)
+		dev_err(&spec->pdev->dev, "invalid msi control: 0x%08x\n",
+			value);
+	value = 0xa50000 | (value & 0xffff);
+	gennum_writel(spec, value, GNPPCI_MSI_CONTROL);
+
+	/*
+	 * Now check the two least-significant bits of the msi-data register,
+	 * then enable CFG_0 or .. CFG_3 accordingly, to get proper vector.
+	 */
+	value = gennum_readl(spec, GNPPCI_MSI_DATA);
+	for (i = 0; i < 7; i++)
+		gennum_writel(spec, 0, GNINT_CFG(i));
+	gennum_writel(spec, 0x800c, GNINT_CFG(value & 0x03));
+
+	/* Finally, ensure we are able to receive it */
+	spec->irq_count = 0;
+	init_completion(&spec->compl);
+	fmc->op->irq_request(fmc, spec_test_handler, "spec-test", 0);
+	gennum_writel(spec, 8, GNINT_STAT);
+	gennum_writel(spec, 0, GNINT_STAT);
+	wait_for_completion_timeout(&spec->compl, msecs_to_jiffies(50));
+	fmc->op->irq_free(fmc);
+	if (!spec->irq_count) {
+		dev_err(&spec->pdev->dev, "Can't receive interrupt\n");
+		return -EIO;
+	}
+	dev_info(&spec->pdev->dev, "Interrupts work as expected\n");
+
+	/* FIXME: configure the GPIO pins to receive interrupts */
+
+	return 0;
+}
+
+static void spec_irq_exit(struct fmc_device *fmc)
+{
+	struct spec_dev *spec = fmc->carrier_data;
+	int i;
+
+	for (i = 0; i < 7; i++)
+		gennum_writel(spec, 0, GNINT_CFG(i));
+	fmc->op->irq_ack(fmc); /* just to be safe */
+}
+
 int spec_fmc_create(struct spec_dev *spec)
 {
 	struct fmc_device *fmc;
@@ -25,24 +133,32 @@ int spec_fmc_create(struct spec_dev *spec)
 	fmc->carrier_data = spec;
 	fmc->base = spec->remap[0];
 	fmc->irq = spec->pdev->irq;
+	fmc->op = &spec_fmc_operations;
+	spec->fmc = fmc;
 
 	ret = spec_i2c_init(fmc);
-	if (ret) {
-		kfree(fmc);
-		return ret;
-	}
-	spec->fmc = fmc;
+	if (ret)
+		goto out_free;
+	ret = spec_irq_init(fmc);
+	if (ret)
+		goto out_free;
 	ret = fmc_device_register(fmc);
-	if (ret) {
-		spec->fmc = NULL;
-		kfree(fmc);
-	}
+	if (ret)
+		goto out_irq;
+	return ret;
+
+out_irq:
+	spec_irq_exit(fmc);
+out_free:
+	spec->fmc = NULL;
+	kfree(fmc);
 	return ret;
 }
 
 void spec_fmc_destroy(struct spec_dev *spec)
 {
 	fmc_device_unregister(spec->fmc);
+	spec_irq_exit(spec->fmc);
 	spec_i2c_exit(spec->fmc);
 	spec->fmc = NULL;
 }
-- 
GitLab