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