Commit a6b133ef authored by Tomasz Wlostowski's avatar Tomasz Wlostowski Committed by Alessandro Rubini

kernel: built-in support for the VIC interrupt controller

parent eb9b9d03
......@@ -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
......
/*
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
......@@ -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;
......
/*
* 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? */
}
......@@ -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__ */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment