forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
221 lines
5.7 KiB
221 lines
5.7 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* Copyright (C) 2015 - Ben Herrenschmidt, IBM Corp. |
|
* |
|
* Driver for Aspeed "new" VIC as found in SoC generation 3 and later |
|
* |
|
* Based on irq-vic.c: |
|
* |
|
* Copyright (C) 1999 - 2003 ARM Limited |
|
* Copyright (C) 2000 Deep Blue Solutions Ltd |
|
*/ |
|
|
|
#include <linux/export.h> |
|
#include <linux/init.h> |
|
#include <linux/list.h> |
|
#include <linux/io.h> |
|
#include <linux/irq.h> |
|
#include <linux/irqchip.h> |
|
#include <linux/irqchip/chained_irq.h> |
|
#include <linux/irqdomain.h> |
|
#include <linux/of.h> |
|
#include <linux/of_address.h> |
|
#include <linux/of_irq.h> |
|
#include <linux/syscore_ops.h> |
|
#include <linux/device.h> |
|
#include <linux/slab.h> |
|
|
|
#include <asm/exception.h> |
|
#include <asm/irq.h> |
|
|
|
/* These definitions correspond to the "new mapping" of the |
|
* register set that interleaves "high" and "low". The offsets |
|
* below are for the "low" register, add 4 to get to the high one |
|
*/ |
|
#define AVIC_IRQ_STATUS 0x00 |
|
#define AVIC_FIQ_STATUS 0x08 |
|
#define AVIC_RAW_STATUS 0x10 |
|
#define AVIC_INT_SELECT 0x18 |
|
#define AVIC_INT_ENABLE 0x20 |
|
#define AVIC_INT_ENABLE_CLR 0x28 |
|
#define AVIC_INT_TRIGGER 0x30 |
|
#define AVIC_INT_TRIGGER_CLR 0x38 |
|
#define AVIC_INT_SENSE 0x40 |
|
#define AVIC_INT_DUAL_EDGE 0x48 |
|
#define AVIC_INT_EVENT 0x50 |
|
#define AVIC_EDGE_CLR 0x58 |
|
#define AVIC_EDGE_STATUS 0x60 |
|
|
|
#define NUM_IRQS 64 |
|
|
|
struct aspeed_vic { |
|
void __iomem *base; |
|
u32 edge_sources[2]; |
|
struct irq_domain *dom; |
|
}; |
|
static struct aspeed_vic *system_avic; |
|
|
|
static void vic_init_hw(struct aspeed_vic *vic) |
|
{ |
|
u32 sense; |
|
|
|
/* Disable all interrupts */ |
|
writel(0xffffffff, vic->base + AVIC_INT_ENABLE_CLR); |
|
writel(0xffffffff, vic->base + AVIC_INT_ENABLE_CLR + 4); |
|
|
|
/* Make sure no soft trigger is on */ |
|
writel(0xffffffff, vic->base + AVIC_INT_TRIGGER_CLR); |
|
writel(0xffffffff, vic->base + AVIC_INT_TRIGGER_CLR + 4); |
|
|
|
/* Set everything to be IRQ */ |
|
writel(0, vic->base + AVIC_INT_SELECT); |
|
writel(0, vic->base + AVIC_INT_SELECT + 4); |
|
|
|
/* Some interrupts have a programmable high/low level trigger |
|
* (4 GPIO direct inputs), for now we assume this was configured |
|
* by firmware. We read which ones are edge now. |
|
*/ |
|
sense = readl(vic->base + AVIC_INT_SENSE); |
|
vic->edge_sources[0] = ~sense; |
|
sense = readl(vic->base + AVIC_INT_SENSE + 4); |
|
vic->edge_sources[1] = ~sense; |
|
|
|
/* Clear edge detection latches */ |
|
writel(0xffffffff, vic->base + AVIC_EDGE_CLR); |
|
writel(0xffffffff, vic->base + AVIC_EDGE_CLR + 4); |
|
} |
|
|
|
static void __exception_irq_entry avic_handle_irq(struct pt_regs *regs) |
|
{ |
|
struct aspeed_vic *vic = system_avic; |
|
u32 stat, irq; |
|
|
|
for (;;) { |
|
irq = 0; |
|
stat = readl_relaxed(vic->base + AVIC_IRQ_STATUS); |
|
if (!stat) { |
|
stat = readl_relaxed(vic->base + AVIC_IRQ_STATUS + 4); |
|
irq = 32; |
|
} |
|
if (stat == 0) |
|
break; |
|
irq += ffs(stat) - 1; |
|
handle_domain_irq(vic->dom, irq, regs); |
|
} |
|
} |
|
|
|
static void avic_ack_irq(struct irq_data *d) |
|
{ |
|
struct aspeed_vic *vic = irq_data_get_irq_chip_data(d); |
|
unsigned int sidx = d->hwirq >> 5; |
|
unsigned int sbit = 1u << (d->hwirq & 0x1f); |
|
|
|
/* Clear edge latch for edge interrupts, nop for level */ |
|
if (vic->edge_sources[sidx] & sbit) |
|
writel(sbit, vic->base + AVIC_EDGE_CLR + sidx * 4); |
|
} |
|
|
|
static void avic_mask_irq(struct irq_data *d) |
|
{ |
|
struct aspeed_vic *vic = irq_data_get_irq_chip_data(d); |
|
unsigned int sidx = d->hwirq >> 5; |
|
unsigned int sbit = 1u << (d->hwirq & 0x1f); |
|
|
|
writel(sbit, vic->base + AVIC_INT_ENABLE_CLR + sidx * 4); |
|
} |
|
|
|
static void avic_unmask_irq(struct irq_data *d) |
|
{ |
|
struct aspeed_vic *vic = irq_data_get_irq_chip_data(d); |
|
unsigned int sidx = d->hwirq >> 5; |
|
unsigned int sbit = 1u << (d->hwirq & 0x1f); |
|
|
|
writel(sbit, vic->base + AVIC_INT_ENABLE + sidx * 4); |
|
} |
|
|
|
/* For level irq, faster than going through a nop "ack" and mask */ |
|
static void avic_mask_ack_irq(struct irq_data *d) |
|
{ |
|
struct aspeed_vic *vic = irq_data_get_irq_chip_data(d); |
|
unsigned int sidx = d->hwirq >> 5; |
|
unsigned int sbit = 1u << (d->hwirq & 0x1f); |
|
|
|
/* First mask */ |
|
writel(sbit, vic->base + AVIC_INT_ENABLE_CLR + sidx * 4); |
|
|
|
/* Then clear edge latch for edge interrupts */ |
|
if (vic->edge_sources[sidx] & sbit) |
|
writel(sbit, vic->base + AVIC_EDGE_CLR + sidx * 4); |
|
} |
|
|
|
static struct irq_chip avic_chip = { |
|
.name = "AVIC", |
|
.irq_ack = avic_ack_irq, |
|
.irq_mask = avic_mask_irq, |
|
.irq_unmask = avic_unmask_irq, |
|
.irq_mask_ack = avic_mask_ack_irq, |
|
}; |
|
|
|
static int avic_map(struct irq_domain *d, unsigned int irq, |
|
irq_hw_number_t hwirq) |
|
{ |
|
struct aspeed_vic *vic = d->host_data; |
|
unsigned int sidx = hwirq >> 5; |
|
unsigned int sbit = 1u << (hwirq & 0x1f); |
|
|
|
/* Check if interrupt exists */ |
|
if (sidx > 1) |
|
return -EPERM; |
|
|
|
if (vic->edge_sources[sidx] & sbit) |
|
irq_set_chip_and_handler(irq, &avic_chip, handle_edge_irq); |
|
else |
|
irq_set_chip_and_handler(irq, &avic_chip, handle_level_irq); |
|
irq_set_chip_data(irq, vic); |
|
irq_set_probe(irq); |
|
return 0; |
|
} |
|
|
|
static const struct irq_domain_ops avic_dom_ops = { |
|
.map = avic_map, |
|
.xlate = irq_domain_xlate_onetwocell, |
|
}; |
|
|
|
static int __init avic_of_init(struct device_node *node, |
|
struct device_node *parent) |
|
{ |
|
void __iomem *regs; |
|
struct aspeed_vic *vic; |
|
|
|
if (WARN(parent, "non-root Aspeed VIC not supported")) |
|
return -EINVAL; |
|
if (WARN(system_avic, "duplicate Aspeed VIC not supported")) |
|
return -EINVAL; |
|
|
|
regs = of_iomap(node, 0); |
|
if (WARN_ON(!regs)) |
|
return -EIO; |
|
|
|
vic = kzalloc(sizeof(struct aspeed_vic), GFP_KERNEL); |
|
if (WARN_ON(!vic)) { |
|
iounmap(regs); |
|
return -ENOMEM; |
|
} |
|
vic->base = regs; |
|
|
|
/* Initialize sources, all masked */ |
|
vic_init_hw(vic); |
|
|
|
/* Ready to receive interrupts */ |
|
system_avic = vic; |
|
set_handle_irq(avic_handle_irq); |
|
|
|
/* Register our domain */ |
|
vic->dom = irq_domain_add_simple(node, NUM_IRQS, 0, |
|
&avic_dom_ops, vic); |
|
|
|
return 0; |
|
} |
|
|
|
IRQCHIP_DECLARE(ast2400_vic, "aspeed,ast2400-vic", avic_of_init); |
|
IRQCHIP_DECLARE(ast2500_vic, "aspeed,ast2500-vic", avic_of_init);
|
|
|