From a6b133efc9e704461a96bc533c5977b579623d03 Mon Sep 17 00:00:00 2001 From: Tomasz Wlostowski <tomasz.wlostowski@cern.ch> Date: Wed, 15 Jan 2014 10:37:33 +0100 Subject: [PATCH] kernel: built-in support for the VIC interrupt controller --- kernel/Makefile | 1 + kernel/hw/vic_regs.h | 87 +++++++++++++++++++ kernel/spec-fmc.c | 108 ++++++++++++++++++++--- kernel/spec-vic.c | 200 +++++++++++++++++++++++++++++++++++++++++++ kernel/spec.h | 9 ++ 5 files changed, 394 insertions(+), 11 deletions(-) create mode 100644 kernel/hw/vic_regs.h create mode 100644 kernel/spec-vic.c diff --git a/kernel/Makefile b/kernel/Makefile index e2565d4..d027e9f 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_WR_NIC) += wr-nic.o spec-y = spec-pci.o spec-y += spec-fmc.o spec-y += spec-i2c.o +spec-y += spec-vic.o spec-y += loader-ll.o spec-y += spec-gpio-no.o spec-$(CONFIG_GPIOLIB) += spec-gpio.o diff --git a/kernel/hw/vic_regs.h b/kernel/hw/vic_regs.h new file mode 100644 index 0000000..3d7e5ff --- /dev/null +++ b/kernel/hw/vic_regs.h @@ -0,0 +1,87 @@ +/* + Register definitions for slave core: Vectored Interrupt Controller (VIC) + + * File : vic_regs.h + * Author : auto-generated by wbgen2 from wb_slave_vic.wb + * Created : Thu Oct 25 16:47:27 2012 + * Standard : ANSI C + + THIS FILE WAS GENERATED BY wbgen2 FROM SOURCE FILE wb_slave_vic.wb + DO NOT HAND-EDIT UNLESS IT'S ABSOLUTELY NECESSARY! + +*/ + +#ifndef __WBGEN2_REGDEFS_WB_SLAVE_VIC_WB +#define __WBGEN2_REGDEFS_WB_SLAVE_VIC_WB + +#include <linux/types.h> + +#if defined( __GNUC__) +#define PACKED __attribute__ ((packed)) +#else +#error "Unsupported compiler?" +#endif + +#ifndef __WBGEN2_MACROS_DEFINED__ +#define __WBGEN2_MACROS_DEFINED__ +#define WBGEN2_GEN_MASK(offset, size) (((1<<(size))-1) << (offset)) +#define WBGEN2_GEN_WRITE(value, offset, size) (((value) & ((1<<(size))-1)) << (offset)) +#define WBGEN2_GEN_READ(reg, offset, size) (((reg) >> (offset)) & ((1<<(size))-1)) +#define WBGEN2_SIGN_EXTEND(value, bits) (((value) & (1<<bits) ? ~((1<<(bits))-1): 0 ) | (value)) +#endif + + +/* definitions for register: VIC Control Register */ + +/* definitions for field: VIC Enable in reg: VIC Control Register */ +#define VIC_CTL_ENABLE WBGEN2_GEN_MASK(0, 1) + +/* definitions for field: VIC output polarity in reg: VIC Control Register */ +#define VIC_CTL_POL WBGEN2_GEN_MASK(1, 1) + +/* definitions for field: Emulate Edge sensitive output in reg: VIC Control Register */ +#define VIC_CTL_EMU_EDGE WBGEN2_GEN_MASK(2, 1) + +/* definitions for field: Emulated Edge pulse timer in reg: VIC Control Register */ +#define VIC_CTL_EMU_LEN_MASK WBGEN2_GEN_MASK(3, 16) +#define VIC_CTL_EMU_LEN_SHIFT 3 +#define VIC_CTL_EMU_LEN_W(value) WBGEN2_GEN_WRITE(value, 3, 16) +#define VIC_CTL_EMU_LEN_R(reg) WBGEN2_GEN_READ(reg, 3, 16) + +/* definitions for register: Raw Interrupt Status Register */ + +/* definitions for register: Interrupt Enable Register */ + +/* definitions for register: Interrupt Disable Register */ + +/* definitions for register: Interrupt Mask Register */ + +/* definitions for register: Vector Address Register */ + +/* definitions for register: Software Interrupt Register */ + +/* definitions for register: End Of Interrupt Acknowledge Register */ +/* definitions for RAM: Interrupt Vector Table */ +#define VIC_IVT_RAM_BYTES 0x00000080 /* size in bytes */ +#define VIC_IVT_RAM_WORDS 0x00000020 /* size in 32-bit words, 32-bit aligned */ +#define VIC_IVT_RAM_BASE 0x00000080 + +/* [0x0]: REG VIC Control Register */ +#define VIC_REG_CTL 0x00000000 +/* [0x4]: REG Raw Interrupt Status Register */ +#define VIC_REG_RISR 0x00000004 +/* [0x8]: REG Interrupt Enable Register */ +#define VIC_REG_IER 0x00000008 +/* [0xc]: REG Interrupt Disable Register */ +#define VIC_REG_IDR 0x0000000c +/* [0x10]: REG Interrupt Mask Register */ +#define VIC_REG_IMR 0x00000010 +/* [0x14]: REG Vector Address Register */ +#define VIC_REG_VAR 0x00000014 +/* [0x18]: REG Software Interrupt Register */ +#define VIC_REG_SWIR 0x00000018 +/* [0x1c]: REG End Of Interrupt Acknowledge Register */ +#define VIC_REG_EOIR 0x0000001c + + +#endif diff --git a/kernel/spec-fmc.c b/kernel/spec-fmc.c index 5e56c5d..83138bb 100644 --- a/kernel/spec-fmc.c +++ b/kernel/spec-fmc.c @@ -93,14 +93,15 @@ out: return ret; } -static int spec_irq_request(struct fmc_device *fmc, irq_handler_t handler, - char *name, int flags) +/* Low-level IRQ request function: set up handler, with or without the VIC */ +static int spec_shared_irq_request(struct fmc_device *fmc, + irq_handler_t handler, char *name, int flags) { struct spec_dev *spec = fmc->carrier_data; int ret; u32 value; - ret = request_irq(fmc->irq, handler, flags, name, fmc); + ret = request_irq(spec->pdev->irq, handler, flags, name, fmc); if (ret) return ret; @@ -118,7 +119,67 @@ static int spec_irq_request(struct fmc_device *fmc, irq_handler_t handler, return 0; } -static void spec_irq_ack(struct fmc_device *fmc) +static void spec_shared_irq_ack(struct fmc_device *fmc); + +static irqreturn_t spec_vic_irq_handler(int id, void *data) +{ + struct fmc_device *fmc = (struct fmc_device *)data; + irqreturn_t rv; + + rv = spec_vic_irq_dispatch((struct spec_dev *)fmc->carrier_data); + + spec_shared_irq_ack(fmc); + return IRQ_HANDLED; +} + +static struct fmc_gpio spec_vic_gpio_cfg[] = { + { + .gpio = FMC_GPIO_IRQ(1), + .mode = GPIOF_DIR_IN, + .irqmode = IRQF_TRIGGER_RISING, + } +}; + +static int spec_irq_request(struct fmc_device *fmc, irq_handler_t handler, + char *name, int flags) +{ + struct spec_dev *spec = fmc->carrier_data; + int rv; + + /* VIC mode interrupt */ + if (!(flags & IRQF_SHARED)) { + int first_time = !spec->vic; + + /* configure the VIC */ + rv = spec_vic_irq_request(spec, fmc, fmc->irq, handler); + + if (rv) + return rv; + + /* on first IRQ, configure VIC "master" handler and GPIO too */ + if (first_time) { + rv = spec_shared_irq_request(fmc, spec_vic_irq_handler, + "spec-vic", IRQF_SHARED); + if (rv) + return rv; + + fmc->op->gpio_config(fmc, spec_vic_gpio_cfg, + ARRAY_SIZE(spec_vic_gpio_cfg)); + } + + } else { + rv = spec_shared_irq_request(fmc, handler, name, flags); + printk("Requesting irq '%s' in shared mode (rv %d)\n", name, + rv); + } + + if (!rv) + spec->flags |= SPEC_FLAG_IRQS_REQUESTED; + + return rv; +} + +static void spec_shared_irq_ack(struct fmc_device *fmc) { struct spec_dev *spec = fmc->carrier_data; @@ -130,15 +191,35 @@ static void spec_irq_ack(struct fmc_device *fmc) gennum_readl(spec, GNGPIO_INT_STATUS); } -static int spec_irq_free(struct fmc_device *fmc) +static void spec_irq_ack(struct fmc_device *fmc) { struct spec_dev *spec = fmc->carrier_data; - gennum_writel(spec, 0xffff, GNGPIO_INT_MASK_SET); /* disable */ - free_irq(fmc->irq, fmc); + if (!spec->vic) + spec_shared_irq_ack(fmc); + + /* Nothing for VIC here, all irqs are acked by master VIC handler */ +} + +static int spec_shared_irq_free(struct fmc_device *fmc) +{ + struct spec_dev *spec = fmc->carrier_data; + + gennum_writel(spec, 0xffff, GNGPIO_INT_MASK_SET); /* disable */ + free_irq(spec->pdev->irq, fmc); return 0; } +static int spec_irq_free(struct fmc_device *fmc) +{ + struct spec_dev *spec = fmc->carrier_data; + + if (spec->vic) + return spec_vic_irq_free(spec, spec->pdev->irq); + else + return spec_shared_irq_free(fmc); +} + /* This is the mapping from virtual GPIO pin numbers to raw gpio numbers */ struct { int virtual; int raw; @@ -230,8 +311,7 @@ static int spec_gpio_config(struct fmc_device *fmc, struct fmc_gpio *gpio, if (gpio->carrier_name) { /* so, it's ours */ gpio->_gpio = gpio->gpio; - } - else if (!gpio->_gpio) { + } else if (!gpio->_gpio) { /* virtual but not mapped (or poor gpio0) */ i = spec_map_pin(gpio->gpio); if (i < 0) @@ -360,6 +440,13 @@ static void spec_irq_exit(struct fmc_device *fmc) for (i = 0; i < 7; i++) gennum_writel(spec, 0, GNINT_CFG(i)); fmc->op->irq_ack(fmc); /* just to be safe */ + + /* VIC mode: release VIC resources and disable VIC master IRQ line */ + if (spec->vic) { + spec_vic_cleanup(spec); + gennum_writel(spec, 0xffff, GNGPIO_INT_MASK_SET); /* disable */ + free_irq(spec->pdev->irq, fmc); + } } static int check_golden(struct fmc_device *fmc) @@ -392,7 +479,7 @@ static int check_golden(struct fmc_device *fmc) int spec_fmc_create(struct spec_dev *spec) { struct fmc_device *fmc; - struct pci_dev *pdev; + struct pci_dev *pdev; int ret; fmc = kzalloc(sizeof(*fmc), GFP_KERNEL); @@ -408,7 +495,6 @@ int spec_fmc_create(struct spec_dev *spec) fmc->fpga_base = spec->remap[0]; fmc->memlen = 1 << 20; - fmc->irq = spec->pdev->irq; fmc->op = &spec_fmc_operations; fmc->hwdev = &spec->pdev->dev; /* for messages */ spec->fmc = fmc; diff --git a/kernel/spec-vic.c b/kernel/spec-vic.c new file mode 100644 index 0000000..ba0bfee --- /dev/null +++ b/kernel/spec-vic.c @@ -0,0 +1,200 @@ +/* +* Copyright (C) 2013 CERN (www.cern.ch) +* Author: Tomasz Wlostowski <tomasz.wlostowski@cern.ch> +* +* Released according to the GNU GPL, version 2 or any later version +* +* Driver for SPEC (Simple PCI Express FMC carrier) board. +* VIC (Vectored Interrupt Controller) support code. +*/ + +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/fmc.h> +#include <linux/fmc-sdb.h> + +#include "spec.h" + +#include "hw/vic_regs.h" + +#define VIC_MAX_VECTORS 32 + +#define VIC_SDB_VENDOR 0xce42 +#define VIC_SDB_DEVICE 0x0013 + +/* A Vectored Interrupt Controller object */ +struct vic_irq_controller { + /* already-initialized flag */ + int initialized; + /* Base address (FPGA-relative) */ + uint32_t base; + /* Mapped base address of the VIC */ + void *kernel_va; + + /* Vector table */ + struct vector { + /* Saved ID of the vector (for autodetection purposes) */ + int saved_id; + /* Pointer to the assigned handler */ + irq_handler_t handler; + /* FMC device that owns the interrupt */ + struct fmc_device *requestor; + } vectors[VIC_MAX_VECTORS]; +}; + +static inline void vic_writel(struct vic_irq_controller *vic, uint32_t value, + uint32_t offset) +{ + writel(value, vic->kernel_va + offset); +} + +static inline uint32_t vic_readl(struct vic_irq_controller *vic, + uint32_t offset) +{ + return readl(vic->kernel_va + offset); +} + +static int spec_vic_init(struct spec_dev *spec, struct fmc_device *fmc) +{ + int i; + signed long vic_base; + struct vic_irq_controller *vic; + + /* + * Try to look up the VIC in the SDB tree - note that IRQs + * shall be requested after the FMC driver has scanned the SDB tree + */ + vic_base = + fmc_find_sdb_device(fmc->sdb, VIC_SDB_VENDOR, VIC_SDB_DEVICE, NULL); + + if (vic_base < 0) { + dev_err(&spec->pdev->dev, + "VIC controller not found, but a VIC interrupt requested. Wrong gateware?\n"); + return -ENODEV; + } + + dev_info(&spec->pdev->dev, "Found VIC @ 0x%lx\n", vic_base); + + vic = kzalloc(sizeof(struct vic_irq_controller), GFP_KERNEL); + if (!vic) + return -ENOMEM; + + vic->kernel_va = spec->remap[0] + vic_base; + vic->base = (uint32_t) vic_base; + + /* disable all IRQs, copy the vector table with pre-defined IRQ ids */ + vic_writel(vic, 0xffffffff, VIC_REG_IDR); + for (i = 0; i < VIC_MAX_VECTORS; i++) + vic->vectors[i].saved_id = + vic_readl(vic, VIC_IVT_RAM_BASE + 4 * i); + + /* config the VIC output: active high, edge, width = 256 tick (4 us) */ + vic_writel(vic, + VIC_CTL_ENABLE | VIC_CTL_POL | VIC_CTL_EMU_EDGE | + VIC_CTL_EMU_LEN_W(250), VIC_REG_CTL); + + vic->initialized = 1; + spec->vic = vic; + + return 0; +} + +void spec_vic_cleanup(struct spec_dev *spec) +{ + if (!spec->vic) + return; + + /* Disable all irq lines and the VIC in general */ + vic_writel(spec->vic, 0xffffffff, VIC_REG_IDR); + vic_writel(spec->vic, 0, VIC_REG_CTL); + kfree(spec->vic); + spec->vic = NULL; +} + +irqreturn_t spec_vic_irq_dispatch(struct spec_dev *spec) +{ + struct vic_irq_controller *vic = spec->vic; + int index, rv; + struct vector *vec; + + /* + * Our parent IRQ handler: read the index value + * from the Vector Address Register, and find matching handler + */ + index = vic_readl(vic, VIC_REG_VAR) & 0xff; + + if (index >= VIC_MAX_VECTORS) + goto fail; + + vec = &vic->vectors[index]; + if (!vec->handler) + goto fail; + + rv = vec->handler(vec->saved_id, vec->requestor); + + vic_writel(vic, 0, VIC_REG_EOIR); /* ack the irq */ + return rv; + +fail: + return 0; +} + +int spec_vic_irq_request(struct spec_dev *spec, struct fmc_device *fmc, + unsigned long id, irq_handler_t handler) +{ + struct vic_irq_controller *vic; + int rv = 0, i; + + /* First interrupt to be requested? Look up and init the VIC */ + if (!spec->vic) { + rv = spec_vic_init(spec, fmc); + if (rv) + return rv; + } + + vic = spec->vic; + + for (i = 0; i < VIC_MAX_VECTORS; i++) { + /* find vector in stored table, assign handle, enable */ + if (vic->vectors[i].saved_id == id) { + spin_lock(&spec->irq_lock); + + vic_writel(vic, i, VIC_IVT_RAM_BASE + 4 * i); + vic->vectors[i].requestor = fmc; + vic->vectors[i].handler = handler; + vic_writel(vic, (1 << i), VIC_REG_IER); + + spin_unlock(&spec->irq_lock); + return 0; + } + } + + + return -EINVAL; + +} + +int spec_vic_irq_free(struct spec_dev *spec, unsigned long id) +{ + int i; + + for (i = 0; i < VIC_MAX_VECTORS; i++) { + uint32_t vec = spec->vic->vectors[i].saved_id; + if (vec == id) { + spin_lock(&spec->irq_lock); + + vic_writel(spec->vic, 1 << i, VIC_REG_IDR); + vic_writel(spec->vic, vec, VIC_IVT_RAM_BASE + 4 * i); + spec->vic->vectors[i].handler = NULL; + + spin_unlock(&spec->irq_lock); + } + } + + return 0; +} + +void spec_vic_irq_ack(struct spec_dev *spec, unsigned long id) +{ + /* fixme: do we need anything special here? */ +} diff --git a/kernel/spec.h b/kernel/spec.h index fe8cfa0..3e3d7d7 100644 --- a/kernel/spec.h +++ b/kernel/spec.h @@ -33,9 +33,12 @@ struct spec_dev { int irq_count; /* for mezzanine use too */ struct completion compl; struct gpio_chip *gpio; + struct vic_irq_controller *vic; + spinlock_t irq_lock; }; #define SPEC_FLAG_FAKE_EEPROM 0x00000001 +#define SPEC_FLAG_IRQS_REQUESTED 0x00000002 /* Registers for GN4124 access */ enum { @@ -141,5 +144,11 @@ extern int spec_eeprom_write(struct fmc_device *fmc, uint32_t offset, extern int spec_gpio_init(struct fmc_device *fmc); extern void spec_gpio_exit(struct fmc_device *fmc); +int spec_vic_irq_request(struct spec_dev *spec, struct fmc_device *fmc, + unsigned long id, irq_handler_t handler); + +int spec_vic_irq_free(struct spec_dev *spec, unsigned long id); +irqreturn_t spec_vic_irq_dispatch(struct spec_dev *spec); +void spec_vic_cleanup(struct spec_dev *spec); #endif /* __SPEC_H__ */ -- GitLab